Ansible wrongly reports 'command not found' error - ansible

I am new to Ansible and I cannot solve an error: I use the ansible.builtin.shell to call the pcs utility (Pacemaker). The pcs is installed on the remote machine, and I can use it when I ssh that machine, but Ansible reports a 'command not found' error with error code 127.
Here is my inventory.yml:
---
all:
children:
centos7:
hosts:
UVMEL7:
ansible_host: UVMEL7
Here is my play-book, TestPcs.yaml:
---
- name: Test the execution of pcs command
hosts: UVMEL7
tasks:
- name: Call echo
ansible.builtin.shell: echo
- name: pcs
ansible.builtin.shell: pcs
Note: I also used the echo command to verify that I am corectly using ansible.builtin.shell.
I launch my play-book with: ansible-playbook -i inventory.yml TestPcs.yaml --user=traite
And I get this result:
PLAY [Test the execution of pcs command] *****************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************
ok: [UVMEL7]
TASK [Call echo] *****************************************************************************************************************************************************************************************************************************
changed: [UVMEL7]
TASK [pcs] ***********************************************************************************************************************************************************************************************************************************
fatal: [UVMEL7]: FAILED! => {"changed": true, "cmd": "pcs", "delta": "0:00:00.003490", "end": "2022-03-10 15:02:17.418475", "msg": "non-zero return code", "rc": 127, "start": "2022-03-10 15:02:17.414985", "stderr": "/bin/sh: pcs : commande introuvable", "stderr_lines": ["/bin/sh: pcs : commande introuvable"], "stdout": "", "stdout_lines": []}
PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
UVMEL7 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
The pcs command is failing and in stderr, there is a 'command not found' error.
On the other and, when I ssh the machine and run pcs command, the command is executed and returns 1 which is different from 127. It is normal that pcs returns an error: I simplified the test case to the strict minimum to keep my question short.
I expect Ansible to have the same behavior: Error on pcs with return code 1.

Here is what I did to simulate what Ansible does (Based on remarks by #Zeitounator): ssh <user>#<machine> '/bin/bash -c "echo $PATH"'
I get my default PATH as explained in the manual page of bash. In my system sh links to bash.
I see that /etc/profile does the path manipulation that I need. However, it seems that because of the option -c, the bash is not started as login shell and therefore etc/profile is not sourced.
I end up doing the job manually:
---
- name: Test the execution of pcs command
hosts: UVMEL7
tasks:
- name: Call echo
ansible.builtin.shell: echo
- name: pcs
ansible.builtin.shell: source /etc/profile && pcs
Which executes pcs as expected.
To sum up, my executable was not executed because the folder holding it was not listed in my PATH environment variable. This was due to the fact that /bin/sh aka /bin/bash was called with the flag -c which prevents sourcing /etc/profile and other configuration files. The issue was 'solved' by sourcing manually the configuration file that correctly sets the PATH environment variable.

Related

How to hide unreacheble hosts?

I need to execute some commands through the shell module, but when I execute them on a group of hosts, they are displayed in the terminal unreachable. How to make it so that information is displayed only on available hosts?
For now, running
ansible all -m shell -a "df -h"
Results in:
Mint-5302 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.53.2 port 22: No route to host",
"unreachable": true
}
You can find the documentation here
Ignoring unreachable host errors
- name: Execute shell
shell: "df -h"
ignore_unreachable: yes
And at the playbook level, to ignoring each unreachable's hosts
- hosts: all
ignore_unreachable: yes
tasks:
- name: Execute shell
shell: "df -h"
You can achieve this behavior by using community.general.diy callback plugin.
Create ansible.cfg file with following content -
[defaults]
bin_ansible_callbacks = True
stdout_callback = community.general.diy
[callback_diy]
runner_on_unreachable_msg=""
Run your ad-hoc command and you will get the following output
$ ansible -m ping 192.168.10.1
PLAY [Ansible Ad-Hoc] *************************************************************************
TASK [ping] ***********************************************************************************
PLAY RECAP ************************************************************************************
192.168.10.1 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0

Ansible play to upload ftp file using lftp

I setup ftp server using ansible and verified it's working by manually doing lftp from my localhost
I uploaded a file using put and was able to find the file in ftp server, when i do ls.
I wrote a play to upload file to ftp server and it returned success always. However, i can't find the file that I uploaded using my play. Then i edited my play to display result register and found a warning. See below
---
- name: test ftp upload
hosts: localhost
tasks:
- name: install lftp
yum:
name: lftp
- name: upload file
shell: >
lftp ansible1.example.com<<EOF
cd pub
put /etc/hosts
bye
EOF
register: result
- name: display result
debug:
var: result
Below is the output I got after running the play
PLAY [test ftp upload] ****************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************************
ok: [localhost]
TASK [install lftp] *******************************************************************************************************************************************************************
ok: [localhost]
TASK [upload file] ********************************************************************************************************************************************************************
changed: [localhost]
TASK [display result] *****************************************************************************************************************************************************************
ok: [localhost] => {
"result": {
"changed": true,
"cmd": "lftp ansible1.example.com<<EOF cd pub put /etc/hosts bye EOF\n",
"delta": "0:00:00.010150",
"end": "2020-04-04 09:15:26.305530",
"failed": false,
"rc": 0,
"start": "2020-04-04 09:15:26.295380",
"stderr": "/bin/sh: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')",
"stderr_lines": [
"/bin/sh: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')"
],
"stdout": "",
"stdout_lines": []
}
}
PLAY RECAP ****************************************************************************************************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I am not able to understand the warning w.r.t EOF part and if it's the reason for the file upload not happening. Because, I use the below shell script (which is basically the same steps from the play) to upload file and it works perfect.
lftp ansible1.example.com<<EOF
cd pub
put /etc/hosts
bye
EOF
You have used the wrong scalar block style indicator (see documentation) for your shell command.
You need to keep the new lines in your piece of shell code. In this case, you have to use the literal scalar block indicator: |
You have used the folded scalar block indicator (>) which will basically convert new lines to spaces (although it is a little more complicated as explained in this other source). So your current piece of shell code expands to a single line in your shell (you can debug the value to see for yourself):
lftp ansible1.example.com<<EOF cd pub put /etc/hosts bye EOF
The following should fix your current problem:
- name: upload file
shell: |
lftp ansible1.example.com<<EOF
cd pub
put /etc/hosts
bye
EOF
register: result

VM's are not Visible to virsh command executed using ansible shell task

I am trying to save the state of all running VM's before I can shut down the host.
virsh save <domain-name> <filetosave>
The idea is to restore the VM's when I bring back the host again.
I am using ansible virt module to determine the running VM's. There is no command available for saving the virtual machine using virt module hence using shell module like below
tasks:
- name: Gathering Facts
setup:
# Listing VMs
- name: list all VMs
virt:
command: list_vms
register: all_vms
- name: list only running VMs
virt:
command: list_vms
state: running
register: running_vms
- name: Print All VM'state
debug:
msg: |
VM's: {{ all_vms.list_vms }},
Active VM's: {{ running_vms.list_vms }}
- name: Save Running VM's
shell: >
virsh save {{ item }} ~/.mykvm/{{item}}.save
args:
chdir: /home/sharu
executable: /bin/bash
loop:
"{{ running_vms.list_vms }}"
register: save_result
- debug:
var: save_result
Output:
TASK [list all VMs]
*********
ok: [192.168.0.113]
TASK [list only running VMs]
*********
ok: [192.168.0.113]
TASK [Print All VM'state]
*********
ok: [192.168.0.113] => {
"msg": "VM's: [u'win10-2', u'ubuntu18.04', u'ubuntu18.04-2', u'win10'],\nActive VM's: [u'win10-2']\n"
}
TASK [Save Running VM's]
*********
failed: [192.168.0.113] (item=win10-2) => {"ansible_loop_var": "item", "changed": true, "cmd": "virsh save win10-2 ~/.mykvm/win10-2.save\n", "delta": "0:00:00.101916", "end": "2019-12-30 01:19:32.205584", "item": "win10-2", "msg": "non-zero return code", "rc": 1, "start": "2019-12-30 01:19:32.103668", "stderr": "error: failed to get domain 'win10-2'", "stderr_lines": ["error: failed to get domain 'win10-2'"], "stdout": "", "stdout_lines": []}
PLAY RECAP
******************
192.168.0.113 : ok=5 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Notice the virt module was able to get the information regarding the running domain's(VM's) but the shell command failed to get the domain name. It seems like the VM's are not visible for Ansible's shell task
To debug further I executed the Ansible ad-hoc command below but got the blank result
$ ansible -i ../hosts.ini RIG3600 -m shell -a "virsh list --all"
192.168.0.113 | CHANGED | rc=0 >>
Id Name State
--------------------
The same command works fine in the Linux shell
$ virsh list --all
Id Name State
--------------------------------
3 win10-2 running
- ubuntu18.04 shut off
- ubuntu18.04-2 shut off
- win10 shut off
Am using Ansible version 2.8.3
Not sure am I missing something here, any help is much appreciated.
The virt module, as documented, defaults to using the libvirt URI qemu:///system.
The virsh command, on the other hand, when run as a non-root user, defaults to qemu:///session.
In other words, your virsh command is not talking to the same libvirt instance as your virt tasks.
You can provide an explicit URI to the virsh command using the -c (--connect) option:
virsh -c qemu:///system ...
Or by setting the LIBVIRT_DEFAULT_URI environment variable:
- name: Save Running VM's
shell: >
virsh save {{ item }} ~/.mykvm/{{item}}.save
args:
chdir: /home/sharu
executable: /bin/bash
environment:
LIBVIRT_DEFAULT_URI: qemu:///system
loop:
"{{ running_vms.list_vms }}"
register: save_result
(You can also set environment on the play instead of on an individual task).

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.

How to create folder in $USER $HOME using Ansible

I'm new to Ansible trying to become $USER then create .ssh folder inside $HOME directory and I'm getting Permission denied:
---
- hosts: amazon
gather_facts: False
vars:
ansible_python_interpreter: "/usr/bin/env python3"
account: 'jenkins'
home: "{{out.stdout}}"
tasks:
- name: Create .SSH directory
become: true
become_method: sudo
become_user: "{{account}}"
shell: "echo $HOME"
register: out
- file:
path: "{{home}}/.ssh"
state: directory
My output is:
MacBook-Pro-60:playbooks stefanov$ ansible-playbook variable.yml -v
Using /Users/stefanov/.ansible/ansible.cfg as config file
PLAY [amazon] *************************************************************************************************************************************************************************************
TASK [Create .SSH directory] **********************************************************************************************************************************************************************
changed: [slave] => {"changed": true, "cmd": "echo $HOME", "delta": "0:00:00.001438", "end": "2017-08-21 10:23:34.882835", "rc": 0, "start": "2017-08-21 10:23:34.881397", "stderr": "", "stderr_lines": [], "stdout": "/home/jenkins", "stdout_lines": ["/home/jenkins"]}
TASK [file] ***************************************************************************************************************************************************************************************
fatal: [slave]: FAILED! => {"changed": false, "failed": true, "msg": "There was an issue creating /home/jenkins/.ssh as requested: [Errno 13] Permission denied: b'/home/jenkins/.ssh'", "path": "/home/jenkins/.ssh", "state": "absent"}
to retry, use: --limit #/Users/stefanov/playbooks/variable.retry
PLAY RECAP ****************************************************************************************************************************************************************************************
slave : ok=1 changed=1 unreachable=0 failed=1
I'm guessing - name and - file are dicts and considered different tasks.
And what was executed in - name is no longer valid in - file?
Because I switched to Jenkins user in - name and in - file I'm likely with the account I do SSH.
Then how can I concatenate both tasks in one?
What is the right way to do this?
Another thing how can I do sudo with file module? I can't see such option:
http://docs.ansible.com/ansible/latest/file_module.html
Or should I just do shell: mkdir -pv $HOME/.ssh instead of using file module?
Then how can I concatenate both tasks in one?
You cannot do it, but you can just add become to the second task, which will make it run with the same permissions as the first one:
- file:
path: "{{home}}/.ssh"
state: directory
become: true
become_method: sudo
become_user: "{{account}}"
Another thing how can i do sudo with file module can't see such option
Because become (and other) is not a parameter of a module, but a general declaration for any task (and play).
I'm guessing -name and -file are dicts and considered different tasks.
The first task is shell, not name. You can add name to any task (just like become).

Resources