How to fix ansible problems at fact gathering time? - ansible

When gathering facts, provisioning fails for the first host with the error you see below.
PLAY [surveylab] ************************************************************
GATHERING FACTS *************************************************************
failed: [192.168.56.101] => {"cmd": "/usr/bin/facter --puppet --json", "failed": true, "rc": 2}
msg: [Errno 2] No such file or directory
ok: [192.168.56.102]
This problem can be fixed by doing:
ln -sf /usr/bin/ruby1.9.1 /usr/bin/ruby
Which I want to do as a pre_task in my playbook like this:
pre_tasks:
- name: symlink expected ruby bin from versioned bin
command: ln -sf /usr/bin/ruby1.9.1 /usr/bin/ruby
It doesn't work, the error still shows. The obvious explanation is that when the facts are gathered, the problem still exists... but how are such things done?

you could try to write a separate playbook that just sets the link - there you would have to disable the gathering of the facts:
- name: 'some playbook'
gather_facts: no
might work.

Make sure the facter package is installed on the target machine(s). For example, on RHEL/CentOS:
sudo yum install facter

Related

Ansible script - error with pexpect module

I am writing ansible script to install universal forwarder but stuck using pexpect module.
Error I am getting:
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ModuleNotFoundError: No module named 'pexpect'
fatal: [Server-a]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (pexpect) on **** Python /usr/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}
I tried many workarounds like passing python interpreter in the command line while running ansible script.
Tried with both python versions 2.7 and 3.
I also see that pexpect is installed: pexpect-2.3-py2.7.egg-info
Since the pexpect Python module is needed on the nodes in a version greater or equal to 3.3 and that there is a pip module to install python dependancies, what you could do is to write the pip task needed to upgrade your nodes dependancies to the right version on pexpect.
Something like:
- pip:
name: pexpect>=3.3
- name: This is just an expect demo
expect:
command: read -p 'What is the answer to life, the universe and everything?'
responses:
(?i)answer: 42
Those two would then yield:
TASK [pip] *******************************************************************
changed: [localhost]
TASK [This is just an expect demo] *******************************************
changed: [localhost]

Fast idempotent Ansible apt deb url

It looks like like command apt install https://github.com/jgraph/drawio-desktop/releases/download/v12.9.3/draw.io-amd64-12.9.3.deb could take long time each time when is invoked even if the package is installed already. It is literally downloads the package each time.
And yes, with ansible is idempotent, with status changed: no.
- name: Install a drawio-desktop .deb package
apt:
deb: https://github.com/jgraph/drawio-desktop/releases/download/v12.9.3/draw.io-amd64-12.9.3.deb
when: ansible_facts['lsb']['id'] == "Ubuntu"
tags:
- debug
- not-macos
Is there any short way to skip download if package installed ?
Ideally will be to say in name that I want to install draw.io if not installed from deb: url else consider things installed.
- name: Install a drawio-desktop .deb package
apt:
name: draw.io
deb: https://github.com/jgraph/drawio-desktop/releases/download/v12.9.3/draw.io-amd64-12.9.3.deb
but is not working like that
TASK [desktop : Install a drawio-desktop .deb package] *********
fatal: [tuxedo]: FAILED! => {"changed": false, "msg": "parameters are mutually exclusive: deb|package|upgrade"}
Any suggestion on a lighter solution to speed up the task?
The behavior seems to be intended according the parameter deb
Ansible will attempt to download deb before installing.
and the current source of apt.py.
So you may have a look into the module package_facts
- name: Gather Package Facts
package_facts:
manager: apt # default ["auto"]
as well a Conditional Example of
when: "ansible_facts['lsb']['id'] == 'Ubuntu' and 'draw.io' not in ansible_facts.packages"
Credits to
How to get the installed apt packages with Ansible?
Further Q&A
Ansible: Do task if apt package is missing
An other approach might be to have the latest package always internally (cached) available and provide a .list file for the native package manager, pointing to the internal repository URL (file share).
By doing this, you could then just use
- name: Install a drawio-desktop .deb package
apt:
name: draw.io
state: latest
without further checks. This will address required updates too.

How 'not to run multiple times' command/shell task in Ansible playbook

I have a task in Ansible to install SASS utilities via RubyGems option, which works well without any issues. If I re-run the same playbook again, it will try to re-install this utility again.
In this case, how to run it once that are using command or shell usage in Ansible playbook. I have somehow handled it not to run the installation by using 'when' option, but need a guidance for better logic/implementation
Task Info:
- name: Install SASS packages by using npm utility.
command: /usr/bin/npm install -g sass
To eliminate re-running the above command task in Ansible playbook, I am using below validation logic and added 'when' option as provided below. Is this fine or do we have a better way of handling this one?
- name: Validation of SASS packages availability.
shell: /usr/local/bin/sass --version
register: result
- debug:
msg: "{{ result.stdout }}"
- name: Install SASS packages by using npm utility.
command: /usr/bin/npm install -g sass
when: "'No such file or directory' in result.stdout"
Results:
TASK [mean-stack : Validation of SASS packages availability.] ************************************************************************************
changed: [linuxosdev003.local.lab]
TASK [mean-stack : debug] ************************************************************************************************************************
ok: [linuxosdev003.local.lab] => {
"msg": "1.47.0 compiled with dart2js 2.15.1"
}
TASK [mean-stack : Install SASS packages by using npm utility.] **********************************************************************************
skipping: [linuxosdev003.local.lab]
PLAY RECAP ***************************************************************************************************************************************
linuxosdev003.local.lab : ok=6 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
You should avoid command or shell whenever possible, if you want an idempotent playbook. Especially your example - remove sass and try your playbook. It should fail. Either check if the file exists
- name: "Check, if file exists"
stat:
path: "/usr/local/bin/sass"
register: sass_installed
But - as already commented - use an ansible module to install NPM packages, like you would do with system packages (package or yum or apt) or Python modules (with pip). The module is also available in the last RedHat releease 2.9 under https://docs.ansible.com/ansible/2.9/modules/npm_module.html and should be as easy:
- name: "Install NPM package: sass"
npm:
name: "sass"
If you need to install more then one NPM package, you could loop over them
- name: "Install NPM packages"
npm:
name: "{{ item }}"
loop:
- "package1"
- "package2"
As #β.εηοιτ.βε and #TRW already mentioned, using a module will eliminate the need for such checks.
However, if it is as simple as checking whether a path exists, then command module itself can be used like:
- name: Install SASS packages by using npm utility.
command:
cmd: /usr/bin/npm install -g sass
creates: /usr/local/bin/sass
This is equal to your when condition, i.e. command will run when the path given in creates is not present.
Whereby I prefer the solution from #β.εηοιτ.βε and #TRW if package managers are available for the tasks, sometimes it might be necessary to stay on the shell module. For such cases I use the similar approach like in the original question.
---
- hosts: test.example.com
become: no
gather_facts: no
tasks:
- name: Gather installed Java version, if there is any
shell:
cmd: java -version 2>&1 | head -1 | cut -d '"' -f 2
register: result
check_mode: false
changed_when: false
failed_when: result.rc != 0 and result.rc != 127
- name: Set default version, if there is no
set_fact:
result:
stdout_lines: "0.0.0_000"
when: "'command not found' in result.stdout"
check_mode: false
- name: Report result
debug:
msg: "{{ result.stdout_lines }}"
check_mode: false
Based on the installed version an installer or updater would be called to install or update to the latest version if necessary.
Thanks to
Why does Java version go to stderr?

I have copied the ansible "copy" module to the project but the copied module does not work

I have developed the ansible modules.
I have copied the ansible's copy module's source code copy.py to the project's root directory as c_copy.py("c_copy" module), and execute the task used the "c_copy" module. As a result, I can't understand but the task have failed although the task used the "copy" module have suceeeded.
It seems that the "c_copy" module can't find files in the role's files directory.
So, please tell me why "c_copy" module does not work at well.
To ask your help, I have created the github repository.
https://github.com/suzuki-shunsuke/ansible-module-test-example
You can check my source code and reproduce my problem.
Requirements to reproduce
Python 2.7.12(pyenv)
pip
Vagrant(My Vagrant version is 1.8.6)
Setup
$ git clone https://github.com/suzuki-shunsuke/ansible-module-test-example
$ cd ansible-module-test-example
$ pyenv install 2.7.12
$ pip install --upgrade pip
$ pip install --upgrade virtualenv
$ virtualenv env
$ source env/bin/activate
$ cp env/lib/python2.7/site-packages/ansible/modules/core/files/copy.py c_copy.py
Test
$ vagrant up --provision-with=ansible
ansible.cfg
[defaults]
library = .
tasks/main.yml
- name: run the copy module
copy:
src: test.txt
dest: /tmp/test.txt
- name: run the clone of the copy module
c_copy:
src: test.txt
dest: /tmp/c_test.txt
Result
TASK [copy : run the copy module] **********************************************
changed: [default]
TASK [copy : run the clone of the copy module] *********************************
fatal: [default]: FAILED! => {"changed": false, "failed": true, "msg": "Source test.txt not found"}
Some modules are wrapped with action plugins.
Here is one for copy module.
So when you call copy, Ansible executes action plugin copy, which in turn calls copy module.
When there is no action plugin, module with the same name called directly.
If you look into copy action plugin code, you'll notice a bunch of file handling stuff, that your c_copy module omits.
Try to duplicate action plugin code and test your setup again.

How to make Ansible execute a shell script if a package is not installed

How can I make Ansible execute a shell script if a (rpm) package is not installed? Is it somehow possible to leverage the yum module?
I don't think the yum module would help in this case. It currently has 3 states: absent, present, and latest. Since it sounds like you don't want to actually install or remove the package (at least at this point) then you would need to do this in two manual steps. The first task would check to see if the package exists, then the second task would invoke a command based on the output of the first command.
If you use "rpm -q" to check if a package exists then the output would look like this for a package that exists:
# rpm -q httpd
httpd-2.2.15-15.el6.centos.1.x86_64
and like this if the package doesn't exist:
# rpm -q httpdfoo
package httpdfoo is not installed
So your ansible tasks would look something like this:
- name: Check if foo.rpm is installed
command: rpm -q foo.rpm
register: rpm_check
- name: Execute script if foo.rpm is not installed
command: somescript
when: rpm_check.stdout.find('is not installed') != -1
The rpm command will also exit with a 0 if the package exists, or a 1 if the package isn't found, so another possibility is to use:
when: rpm_check.rc == 1
Based on the Bruce P answer above, a similar approach for apt/deb files is
- name: Check if foo is installed
command: dpkg-query -l foo
register: deb_check
- name: Execute script if foo is not installed
command: somescript
when: deb_check.stdout.find('no packages found') != -1
If the package is installable through the system package manager (yum, apt, etc) itself, then you can make use of the check mode flag of ansible to register installation status without actually installing the package.
- name: check if package is installed
package:
name: mypackage
state: present
check_mode: true
register: mypackage_check
- name: run script if package installed
shell: myscript.sh
when: not mypackage_check.changed
Related to this.
Putting everything together, complete playbook for Debian (Ubuntu) which Updates package only if it's already installed:
---
- name: Update package only if already installed (Debian)
hosts: all
sudo: yes
tasks:
- name: Check if Package is installed
shell: dpkg-query -W -f='${Status}' {{ package }} | grep 'install ok installed'
register: is_installed
failed_when: no
changed_when: no
- name: Update Package only if installed
apt:
name: {{ package }}
state: latest
update_cache: yes
when: is_installed.rc == 0
Sadly Ansible still hasn't built-in support for making simple package updating, see ex: https://github.com/ansible/ansible/issues/10856
Since Ansible 2.5, you can use the package_facts module:
- name: Gather package facts
package_facts:
manager: auto
- name: Debug if package is present
debug:
msg: 'yes, mypackage is present'
when: '"mypackage" in ansible_facts.packages'
- name: Debug if package is absent
debug:
msg: 'no, mypackage is absent'
when: '"mypackage" not in ansible_facts.packages'
Note: you need the python bindings for apt/rpm installed on the target, e.g. python-apt for Debian.
You shouldn't be using dpkg -l package because it has no idea if your package has been removed or is still installed.
Instead it's probably better to use dpkg -s package.
To check if the package is installed :
- shell: dpkg -s package | grep 'install ok installed'
or if you don't mind the package on hold or other states :
- shell: dpkg -s package | grep 'installed'
This return 0 when installed and 1 if not.
(It's important to use the shell as we are using a pipe |)
I find using shell or command module is not "ansiblic".
I prefer to use yum module and json_query filter to check if a package is already installed. E.g. httpd package :
- yum:
list: httpd
register: apache_service
- assert:
that:
- "'installed' in apache_service|json_query('results[*].yumstate')"
msg: 'httpd is not installed'

Resources