I want to download some binaries like Helm and make them globally available using $PATH
. To avoid downloading with root privileges or alternatively have two steps (download with standard user and move to some folder in $PATH
like /usr/local/bin
), my idea was to create $HOME/bin
and add it to $PATH
.
This blog article was used to add a custom path to /etc/environment
. Then I reload it as described here. This is my POC playbook where I try to add exa:
- name: Test
hosts: all
vars:
- bin: /home/vagrant/bin
tasks:
- name: Test task
file:
path: "{{bin}}"
state: directory
- name: Add {{bin}} to path
become: yes
lineinfile: >
dest=/etc/environment
state=present
backrefs=yes
regexp='PATH=(["]*)((?!.*?{{bin}}).*?)(["]*)$'
line="PATH=\1\2:{{bin}}\3"
- name: Check path1
shell: echo $PATH
- name: Download exa
unarchive:
src: https://github.com/ogham/exa/releases/download/v0.8.0/exa-linux-x86_64-0.8.0.zip
dest: "{{bin}}"
remote_src: yes
- name: reload env file
shell: for env in $( cat /etc/environment ); do export $(echo $env | sed -e 's/"//g'); done
- name: Check path after reload env file
shell: echo $PATH
- name: Test exa from PATH
shell: exa-linux-x86_64 --version
In the last task Test exa from PATH
it throws the error:
"stderr": "/bin/sh: 1: exa-linux-x86_64: not found"
The echo $PATH
commands remain both at
"stdout": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
But the modified /etc/environment
works. When I go to SSH on the machine without Ansible, $PATH
is fine and also exa-linux-x86_64 --version
works:
~$ echo $PATH
/home/vagrant/bin:/home/vagrant/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/vagrant/bin:/snap/bin
Environment
Ubuntu 18.04 host system running Vagrant with a Ubuntu 16.04 box. Ansible was executed by Vagrant on Ubuntu 16.04.
Possible workarounds
Using separate env variable
When setting the environment variable like this
- name: Test exa from PATH
shell: exa-linux-x86_64 --version
environment:
PATH: "{{ ansible_env.PATH }}:{{bin}}"
it works. But I have to apply those lines at least on play-level. I would like to set it globally like /etc/environment
does at regular shells. This questions seems to have the same target, but on Solaris. The answer seems to only set $PATH
from the host, which isn't useful for me since the custom bin dir isn't there.
Use only absolute paths
- name: Test exa from PATH
shell: "{{bin}}/exa-linux-x86_64 --version"
This causes less overhead, but you've to remember prefixing your commands always with the path variable. Seems also error-prone
Understanding the problem
I want a real solution and realize what's causing the problem. It's not clear for me why $PATH
modification is so hard on Ansible, where it can be done quite easily in the underlying Linux system. This question says we don't have an interactive session in Ansible. There seems to be no $PATH
available. According to the documentation, we can archive this by passing -l
to bash. So I found the following working:
- name: Test exa from PATH
shell: bash -l -c "exa-linux-x86_64 --version"
But the following results in an error:
- name: Test exa from PATH
shell: exa-linux-x86_64 --version
args:
executable: /bin/bash -l
Here Ansible breaks the command with wrong quoting of the args:
"'/bin/bash -l' -c 'exa-linux-x86_64 --version'"
This ticket recommends the Ansible team to fix this, so that we get an login shell with $PATH
. Since 2014, no real solution was provided at all.
Questions
- What is the purpose of different shell types that get access to the modified
$PATH
or not? - Why does Ansible complicate things? Wouldn't it be easier to provide a login shell that solves this issue? Are there reasons why they did what they did?
- How can we deal with the resulting problems? What is best practice?