os specific pre_task in ansible - 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
}

Related

Ansible Command Line output

I am having an issue learning Ansible and getting feedback from it.
Here is the code I am running
clear && sudo ansible-playbook patching.yml -u stackoverflow -k --ask-become-pass -v
Here is my patching.yml file
---
- hosts: all
gather_facts: no
become: yes
tasks:
- name: Patching
apt: update_cache=yes force_apt_get=yes
- name: Proof of Patching
shell: apt -q -y --ignore-hold --allow-change-held-packages --allow-unauthenticated -s dist-upgrade | /bin/grep ^Inst | wc -l
register: output
- debug: var=output
What I am trying to accomplish is one to learn Ansible and two to patch my various Linux machines with some sort of feedback that they have patched I am getting this feedback after reading stack overflow for some time. It is long and un-human-friendly I would like it to be just a # or even better hostname: #.
Thank You
-Jared

Is there a way to include github3.py as an Ansible Playbook library or module, without installing via pip?

My playbook needs to be able to support adding a tag to a github repository when a production deployment is performed. The intent is to automate the generation of a release tag via Ansible's built in github_release module as a local action on the Ansible server. The documentation on the module is clear, and straight forward, that this module requires the github3.py Python module.
The issue I'm running into at this point is that our "CICD Operators" do not believe that the github3.py module needs to be installed on the Ansible server. The belief is that 'github3.py' can be put into the /library directory and the module is available no matter what server it runs on. This seems logical based on my limited understanding of Ansible playbook libraries; however, github3 is not just a simple Python script that can be copied into a directory.
Everything I've found shows that this needs to be installed on the server via PIP. Is that the only way to do this, or am I missing some fundamental process to take the source tar ball and add it as a local playbook library? I've tried extracting the source to ./library and even created symbolic links to different points in the structure.
https://docs.ansible.com/ansible/latest/modules/github_release_module.html
https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#bundling-ansible-modules-with-playbooks
https://pypi.org/project/github3.py/1.3.0/
/playbook $ ls
ansible.cfg checkConnectivity.yml gitTagging.yml library roles site.yml
/playbook $ ls -la library/
... dedup_list.py
... github3 -> /home/#####/AppEng_Ansible_Playbook/playbook/library/github3.py-1.3.0/src/github3
... github3.py -> /home/#####/AppEng_Ansible_Playbook/playbook/library/github3.py-1.3.0/src/github3
... github3.py-1.3.0
... github3.py-1.3.0.tar.gz
/playbook $ cat ansible.cfg
[default]
library = ./library
#github3.py = ./library/github3.py
#github3.py = ./library/github3
#github3 = ./library/github3
/playbook $ cat gitTagging.yml
---
- hosts: all
tasks:
- name: Local Action - Create Tag
local_action:
module: github_release
user: XYZ
password: #####
action: create_release
repo: git#repo.XXXXX.com:Enterprise-Communications/web_php_cicd_testing.git
tag: MyTag
target: v0.0.9rc
name: Ansible Test
body: Test 1
run_once: true
ansible-playbook 2.8.2
...
...
TASK [Local Action - Create Tag] ************************************************************************************************************************************************************************************************************
task path: /home/#####/AppEng_Ansible_Playbook/playbook/gitTagging.yml:14
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: #####
<localhost> EXEC /bin/sh -c 'echo ~##### && sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380 `" && echo ansible-tmp-1568051389.77-61244317218380="` echo /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380 `" ) && sleep 0'
Using module file /usr/lib/python2.7/site-packages/ansible/modules/source_control/github_release.py
<localhost> PUT /home/#####/.ansible/tmp/ansible-local-228190qaU7p/tmptpN1cj TO /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380/AnsiballZ_github_release.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380/ /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380/AnsiballZ_github_release.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380/AnsiballZ_github_release.py && sleep 0'
<localhost> EXEC /bin/sh -c 'rm -f -r /home/#####/.ansible/tmp/ansible-tmp-1568051389.77-61244317218380/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_github_release_payload_fZHzWp/__main__.py", line 134, in <module>
import github3
ImportError: No module named github3
fatal: [localhost -> localhost]: FAILED! => {
"changed": false,
"invocation": {
"module_args": {
"action": "create_release",
"body": "Test 1",
"draft": false,
"name": "Ansible Test",
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"prerelease": false,
"repo": "git#repo.XXXXX.com:Enterprise-Communications/web_php_cicd_testing.git",
"tag": "MyTag",
"target": "v0.0.9rc",
"token": null,
"user": "XYZ"
}
},
"msg": "Failed to import the required Python library (github3.py >= 1.0.0a3) on led####'s Python /usr/bin/python. Please read module documentation and install in the appropriate location"
}
You can have the playbook install the necessary dependency into a temporary virtualenv, then use the remainder of the playbook to execute against that venv:
- hosts: localhost
connection: local
tasks:
- raw: |
if [ -x /tmp/bob/bin/python ]; then exit 0; fi
/usr/local/bin/python3 -m venv --prompt tmp /tmp/bob
. /tmp/bob/bin/activate
/tmp/bob/bin/pip install --upgrade 'github3.py >= 1.0.0a3'
- hosts: localhost
connection: local
vars:
ansible_python_interpreter: /tmp/bob/bin/python
tasks:
- github_release:
user: ansible
repo: ansible
action: latest_release
(substituting your own hosts: and connection:; I just used localhost and local for testing whether such a thing would work, and the answer that it does)
It may even be possible to do such a thing in a role, to have some cleanup action or whatever, but I didn't take it that far

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.

Chown Not Permitted, Has full Sudo Access?

I have an Ansible role which I'm trying to migrate to macOS.
The following step fails:
- name: get go user details
command: sh -c 'echo $HOME'
become: true
become_user: "{{ go_user }}"
become_flags: -Hi
changed_when: false
register: go_user_check
The error emitted is:
TASK [default : get go user details] *******************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: /var/tmp/ansible-tmp-1533765311.28-97292171123102/: Operation not permitted\nchown: /var/tmp/ansible-tmp-1533765311.28-97292171123102/command.py: Operation not permitted\n}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user"}
This same step works just fine on all the Linux distributions
I'm testing against. When I execute sudo -Hiu username sh -c 'echo $HOME', I get what I would expect: /Users/travis.
When I execute the following:
ansible -i 127.0.0.1, -c local --become --become-user travis \
-m command -a 'sh -c "echo $HOME"' all
I get exactly what I'd expect, /Users/travis.
I'm reading the documentation linked to by Ansible but I'm not seeing a workaround.
Is there a different way I should be executing this on OSX?
I was able to get it to proceed with the following modification:
diff --git a/tasks/discover/go.yml b/tasks/discover/go.yml
index 090ce7d..f0c2b89 100644
--- a/tasks/discover/go.yml
+++ b/tasks/discover/go.yml
## -3,9 +3,10 ##
command: sh -c 'echo $HOME'
become: true
become_user: "{{ go_user }}"
- become_flags: -Hi
changed_when: false
register: go_user_check
+ vars:
+ ansible_ssh_pipelining: true
- name: set go user home
set_fact: go_user_home="{{ go_user_check.stdout_lines[0] }}"
Pipelining appears to dodge the file modification that is failing. Unfortunately, I don't have the answer as to why it's failing, so other answers are still welcome as I'd like to get to what is actually happening.
There appear to be a lot of caveats to using Ansible on macOS, so I will probably have to have my tasks diverge completely based on whether it's Linux or macOS.

Add binaries to PATH with Ansible

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

Resources