Add binaries to PATH with Ansible - bash

I'm trying to install the Kiex Version manager for the Elixir programming language using Ansible.
These are the plays I use for this:
- name: Kiex Installation
hosts: web
gather_facts: false
remote_user: deployer
tasks:
- shell: \curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s
- name: Add Kiex Bin to Path
lineinfile:
dest: /home/deployer/.bashrc
regexp: '^test -s'
line: '[[ -s "$HOME/.kiex/scripts/kiex" ]] && source "$HOME/.kiex/scripts/kiex"'
- name: Reload Path
shell: source /home/deployer/.bashrc
args:
executable: /bin/bash
- shell: echo $PATH
register: pathul
- debug:
var: pathul
- name: Elixir Installation
hosts: web
gather_facts: false
remote_user: deployer
tasks:
- shell: echo $PATH
register: pathul
- debug:
var: pathul
- name: Install Elixir Version
command: /home/deployer/.kiex/bin/kiex list
args:
executable: /bin/bash
chdir: /home/deployer/
- name: Set Elixir Version as Default
shell: kiex default 1.4
The Installation of Kiex is a success and if I log in to the remote Machine I am able to run it simply by using the kiex command. I can do this because I sourced the binaries in "~/.kiex/scripts/kiex". When I echo the $PATH variable it shows the kiex binaries path /home/deployer/.kiex/bin in it:
$ echo $PATH
/home/deployer/.kiex/bin:/home/deployer/.kiex/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
However the kiex, kiex list and even the /home/deoployer/.kiex/bin/kiex list in the Elixir Installation Play shown above fail with the message:
TASK [Set Elixir Version as Default] *******************************************
fatal: [local-web-2]: FAILED! => {"changed": true, "cmd": "kiex default 1.4", "delta": "0:00:00.002042", "end": "2017-01-26 22:13:32.898082", "failed": true, "rc": 127, "start": "2017-01-26 22:13:32.896040", "stderr": "/bin/sh: 1: kiex: not found", "stdout": "", "stdout_lines": [], "warnings": []}
Also the pathul variable that registered the result of echoing the path via ansible doesn't contain /home/deployer/.kiex/bin:
"stdout": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
How can I make the kiex command work properly via Ansible?

Just use the full, absolute path, like you tried in the Install Elixir Version task, but mind that you have a typo, both, in the example and in the explanation you posted:
command: /home/deoployer/.kiex/bin/kiex list
[ ] even the /home/deoployer/.kiex/bin/kiex list [ ] fail[s]
It should likely be deployer, like in the first play, not deoployer.
There is no reason otherwise for Ansible to fail with "kiex: not found" message, if you provide the correct path.
Explanations regarding other tasks:
Quoting man bash:
When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists.
So your ~/.bashrc is not even read when you execute tasks with Ansible, because it's not an interactive session.
This is for example why your pathul variable does not contain changes applied in the ~/.bashrc.
The following two tasks run separate bash processes. The environment sourced in the first task has no influence on the environment of the second:
- name: Reload Path
shell: source /home/deployer/.bashrc
args:
executable: /bin/bash
- shell: echo $PATH
register: pathul

Related

How to use the 'export' command in Ansible playbook?

If I run the below command directly in terminal, kubectl is getting enabled. If I use the same command with shell module in Ansible playbook, its executing but its not doing its job of enabling the kubectl.
export KUBECONFIG="/etc/rancher/rke2/rke2.yaml" \
&& export PATH="$PATH:/usr/local/bin:/var/lib/rancher/rke2/bin"
Ansible playbook
---
- name: Copy installer
hosts: FIRST_SERVER
gather_facts: yes
ignore_unreachable: true
any_errors_fatal: true
tasks:
- name: Execute enable kubectl on primary server
when: inventory_hostname in groups['FIRST_SERVER']
shell: |
set -o pipefail
export KUBECONFIG="/etc/rancher/rke2/rke2.yaml"
export PATH="$PATH:/usr/local/bin:/var/lib/rancher/rke2/bin"
args:
executable: /bin/bash
become: yes
Please suggest.
Your example is setting remote environment variables for the task temporary only.
For certain servers I am the following approach of
What do the scripts in /etc/profile.d do?
by using
- name: Provide environment variable script file
template:
src: "{{ item }}.j2"
dest: "/etc/profile.d/{{ item }}"
with_items:
- "environment.sh"
and in example
# /etc/profile.d/environment.sh
export ACCOUNT=$(who am i | cut -d " " -f 1)
export DOMAIN=$(hostname | cut -d "." -f 2-4)
Further Q&A
"Scripts placed in ... get sourced on login"
How to set an environment variable during package installation?
By doing this I am able to set persistent environment variables for specific software and services.

os specific pre_task in ansible

I would like to run a set of raw commands (to install python) if a given
operating system on the target host is OpenBSD
I have to run these checks as pre_tasks (because to run anything as a task Python must already be present on the target system)
What I do not understand is, whether any OS characteristics variables are available in pre_task stage (like ansible_os_family)
- name: Check for Python
raw: test -e /usr/bin/python
changed_when: false
failed_when: false
register: check_python
- name: Install Python on OpenBSD
# raw: test -e /usr/bin/apt && (apt -y update && apt install -y python-minimal) || (yum -y install python libselinux-python)
raw: pkg_add -r python%3
when: check_python.rc != 0
when: ansible_os_family == "OpenBSD" # <-- problem here
I seem to get a problem when trying to use ansible_os_family
Is there a way to enable the pre_tasks without me writing my own os family checks?
PS. I used the above python installation code, following recommendation here:
[1] https://relativkreativ.at/articles/how-to-install-python-with-ansible
Following your latest comments, as said earlier by #Vladimir and myself, you will not be able to use ansible's facts variables to detect the OS on hosts without python since it is impossible to gather facts (i.e. play the setup module). You need to do that job on your own for this first step.
I don't have a BSD system I can access. So the below is totally untested for your platform. But I believe it should work, or at least put you on track. Check the content of /etc/os-release on your system to adapt the regex if needed. The one I wrote gave a correct result on Ubuntu, Debian, Centos, RHEL and on RedHat UBI and Alpine docker images.
---
- name: Fix hosts with no python at all
hosts: all
gather_facts: false
tasks:
- name: Perform a dirty OS detection if python is not installed
raw: |-
if which python > /dev/null || which python3 > /dev/null; then
echo installed
else
sed -n "s/^NAME=\"\(.*\)\"/\\1/p" /etc/os-release
fi
changed_when: false
register: dirty_detect
- name: Print message when already installed
debug:
msg: Host {{ inventory_hostname }} already has python. Next will skip.
when: dirty_detect.stdout_lines[0] == 'installed'
- name: Install python on openbsd if needed
raw: |-
pkg_add -r python%3
become: true
when: dirty_detect.stdout_lines[0] == 'OpenBSD'
- name: Install python on Ubuntu if needed
raw: |-
apt install -y python3
become: true
when: dirty_detect.stdout_lines[0] == 'Ubuntu'
- name: Back to normal life with ansible
hosts: all
tasks:
- name: Now we gathered facts and we can use all facts vars
debug:
msg: Host {{ inventory_hostname }} is running {{ ansible_os_familly }}
- name: We might have left hosts with python 2.7 only above, make sure python3 is installed
package:
name: python3
become: true
Q: "Is there a way to enable the pre_tasks without me writing my own os family checks?"
A: No. It is not. Python is needed to collect the facts.
See the verbose output. For example the output of connecting FreeBSD 12 remote host from the Ubuntu controller
shell> ansible --version
ansible 2.9.6
...
python version = 3.8.2 (default, Apr 27 2020, 15:53:34) [GCC 9.3.0]
shell> ansible test_01 -m setup -vvv
ESTABLISH SSH CONNECTION FOR USER: admin
SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="admin"' -o ConnectTimeout=30 -o ControlPath=/export/home/admin.config/.ansible/cp/5a3ab05cf7 test_01 '/bin/sh -c '"'"'/usr/local/bin/python3.7 && sleep 0'"'"''
Without Python installed the module will crash
(127, b'', b'/bin/sh: /usr/bin/python: not found\n')
Failed to connect to the host via ssh: /bin/sh: /usr/bin/python: not found
[WARNING]: No python interpreters found for host test_01 (tried ['/usr/bin/python',
'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-
python', '/usr/bin/python3', 'python'])
test_01 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"module_stderr": "/bin/sh: /usr/bin/python: not found\n",
"module_stdout": "",
"msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
"rc": 127
}

ansible behavior to specific sudo commands on managed nodes

Here to discuss the ansible behavior when user at managed nodes is given sudo privileges to specific commands.
I have sudo privileges on remote managed host [rm-host.company.com] to specific commands. Two of them are:
/bin/mkdir /opt/somedir/unit*
/bin/chmod 2775 /opt/somedir/unit*
PS: /opt/somedir at remote nodes exists already.
My ansible control machine version:
ansible 2.7.10
python version = 2.7.5 (default, Mar 26 2019, 22:13:06) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
YAML code fails when I use ansbile "file" module even though I have sudo privileges to chmod and mkdir as listed above.
- name: 7|Ensure Directory - "/opt/somedir/{{ ENV_CHOSEN }}" Permissions are 2775
become: yes
become_method: sudo
file: path="/opt/somedir/{{ ENV_CHOSEN }}" state=directory mode=2775
when:
- ansible_facts['os_family'] == "CentOS" or ansible_facts['os_family'] == "RedHat"
- ansible_distribution_version | int >= 6
- http_dir_path.stat.exists == true
- http_dir_path.stat.isdir == true
- CreateWebAgentEnvDir is defined
- CreateWebAgentEnvDir is succeeded
register: ChangeDirPermission
- debug:
var: ChangeDirPermission
Runtime error:
TASK [7|Ensure Directory - "/opt/somedir/unitc" Permissions are 2775] **************************************************************************************************************************************************************************************
fatal: [rm-host.company.com]: FAILED! => {"changed": false, "module_stderr": "FIPS mode initialized\r\nShared connection to rm-host.company.com closed.\r\n", "module_stdout": "sudo: a password is required\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
to retry, use: --limit #/u/joker/scripts/Ansible/playbooks/agent/plays/agent_Install.retry
PLAY RECAP ***************************************************************************************************************************************************************************************************************************************************
rm-host.company.com : ok=9 changed=2 unreachable=0 failed=1
But succeeds when I use command module, like so:
- name: 7|Ensure Directory - "/opt/somedir/{{ ENV_CHOSEN }}" Permissions are 2775
command: sudo /bin/chmod 2775 "/opt/somedir/{{ ENV_CHOSEN }}"
when:
- ansible_facts['os_family'] == "CentOS" or ansible_facts['os_family'] == "RedHat"
- ansible_distribution_version | int >= 6
- http_dir_path.stat.exists == true
- http_dir_path.stat.isdir == true
- CreateagentEnvDir is defined
- CreateagentEnvDir is succeeded
register: ChangeDirPermission
- debug:
var: ChangeDirPermission
Success Runtime debug output captured:
TASK [7|Ensure Directory - "/opt/somedir/unitc" Permissions are 2775] **************************************************************************************************************************************************************************************
[WARNING]: Consider using 'become', 'become_method', and 'become_user' rather than running sudo
changed: [rm-host.company.com]
TASK [debug] *************************************************************************************************************************************************************************************************************************************************
ok: [rm-host.company.com] => {
"ChangeDirPermission": {
"changed": true,
"cmd": [
"sudo",
"/bin/chmod",
"2775",
"/opt/somedir/unitc"
],
"delta": "0:00:00.301570",
"end": "2019-06-22 13:20:17.300266",
"failed": false,
"rc": 0,
"start": "2019-06-22 13:20:16.998696",
"stderr": "",
"stderr_lines": [],
"stdout": "",
"stdout_lines": [],
"warnings": [
"Consider using 'become', 'become_method', and 'become_user' rather than running sudo"
]
}
}
Question:
How can I make this work without using command module? I want to stick to ansible core modules using 'become', 'become_method' rather than running sudo in command module.
Note:
It works when sudo is enabled for ALL commands. But [ user ALL=(ALL) NOPASSWD: ALL ] cannot be given on remote host. Not allowed by company policy for the group I am in.
The short answer is you can't. The way ansible works is by executing python scripts in the remote host (except for the raw, command and shell modules). See the docs.
The file module executes this script with a long line of parameters. But ansible will first become the required user, in this case root by running sudo -H -S -n -u root /bin/sh in the remote ssh session (please bear in mind that this command might be slightly different in your case).
Once the user logged remotely has become the root user, Ansible will upload and execute the file.py script.
It looks like in your case, you'll need to revert to use the raw, command or shell in the cases you need to run the privileged commands.
To understand this a bit better and see the detail and order of the commands being executed, run ansible-playbook with the parameter -vvvv.
I solved this issue by removing the become_method and become_user off my playbook.
First, I specified the user in the inventory file using ansible_user=your_user. Then, I removed the become_method and become_user off my playbook leaving just become=yes
For more details about this answer, look on this other answer.

Ansible Local connection script argument path failed to detect

I have a ansible playbook which calls 2 roles. role 1 runs on local, which has a script with arg as file path /tmp/inputfile/input.csv. The playbook looks:
- hosts: "{{my_extra_var_IP}}"
connection: local
roles:
- prereq
Roles task:
- name: Copy script to local
copy:
src: files/csv_to_files.sh
dest: /tmp/input_dir/
mode: 0777
- command: ls -ltr /tmp/input_dir
- command: cat /tmp/input_dir/inputFile.csv
#- name: run csv to yml script
# script: /tmp/input_dir/csv_to_files.sh /tmp/input_dir/inputFile.csv
# become_user: niceha
The output of first 2 tasks is success and is as expected but on 3rd & 4th step I get error:
FAILED! => {"changed": true, "cmd": ["cat", "/tmp/input_dir/inputFile.csv"], "delta": "0:00:00.007141", "end": "2017-06-09 15:53:58.673450", "failed": true, "rc": 1, "start": "2017-06-09 15:53:58.666309", "stderr": "cat: /tmp/input_dir/inputFile.csv: No such file or directory", "stdout": "", "stdout_lines": [], "warnings": []}
I am running this job from tower which uses userA I also tried to change the users but no luck.
The indenting does not look right:
- name: Copy script to local
copy:
src: files/csv_to_files.sh
dest: /tmp/input_dir/
mode: 0777
Ok. So after much reading I got to know the code is fine as it runs from the console but not from the Ansible tower, and just to cross check it worked from other dir paths.
Ansible tower actually uses /tmp/ dir as staging area so any changes/tasks mentioned in the playbook to be run in tmp dir wont take affect.
Changing my input file path from /tmp to /home/user did the work for me.

How to set environmental variables using Ansible

I need to set the variables like JAVA_HOME and update PATH. There are a number of ways of doing this. One way is to update the /etc/environment variable and include a line for JAVA_HOME using the lineinfile module and then run the command source /etc/environment directly on the guest OS (CentOS in my case).
Another way is to execute the export command e.g.
export JAVA_HOME=/usr/java/jre1.8.0_51
export PATH=$PATH:$JAVA_HOME
Is there a cleaner way to do this as all these require manipulating files and running commands directly on the OS to update the environment variables?
Yes, there is a cleaner way. You can set environment variables per task:
tasks:
- shell: echo JAVA_HOME is $JAVA_HOME
environment:
JAVA_HOME: /usr/java/jre1.8.0_51
register: shellout
- debug: var=shellout
Output:
TASK: [shell echo JAVA_HOME is $JAVA_HOME] **********************************
changed: [localhost]
TASK: [debug var=shellout] ****************************************************
ok: [localhost] => {
"var": {
"shellout": {
"changed": true,
"cmd": "echo JAVA_HOME is \"$JAVA_HOME\"",
"delta": "0:00:00.005797",
"end": "2015-08-07 06:32:47.295061",
"invocation": {
"module_args": "echo JAVA_HOME is \"$JAVA_HOME\"",
"module_name": "shell"
},
"rc": 0,
"start": "2015-08-07 06:32:47.289264",
"stderr": "",
"stdout": "JAVA_HOME is /usr/java/jre1.8.0_51",
"stdout_lines": [
"JAVA_HOME is /usr/java/jre1.8.0_51"
],
"warnings": []
}
}
}
If you set the environment variable like above in a task, it is only available for this specific task. In subsequent tasks it does not exist unless you define it again.
Though you can define env vars per play as well:
- hosts:
- localhost
gather_facts: no
environment:
JAVA_HOME: /usr/java/jre1.8.0_51
tasks:
...
Now it's gonna be available for all tasks of this play.
See Setting the Environment and FAQ: How can I set the PATH or any other environment variable for a task or entire playbook? in the docs.
Another example with a script task:
tasks:
- script: /tmp/script.sh
environment:
JAVA_HOME: /usr/java/jre1.8.0_51
register: shellout
- debug: var=shellout
Where the script simply has this content:
#!/bin/sh
echo JAVA_HOME is $JAVA_HOME
I found that a workaround to do this was to use the lineinfile command in Ansible:
- name: Set JAVA_HOME
lineinfile: dest=/etc/environment state=present regexp='^JAVA_HOME' >
line='JAVA_HOME=/opt/jre1.8.0_51/bin'
While this is not ideal, it allows you to create new environmental variables. Of course, you should use variables to construct your directory path. I have included the explicit path to simplify my example.
Update to the lineinfile approach. The JAVA_HOME value should not include the bin directory. The following worked for centos:
- name: Set JAVA_HOME
lineinfile:
dest: /etc/environment
state: present
regexp: '^JAVA_HOME'
line: 'JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk'

Resources