Ansible Run Shell Command Upon Condition in Multiple Hosts - ansible

I have the following script that attempts to install a package on a node only when not already installed.
- name: check if linux-modules-extra-raspi is installed # Task 1
package:
name: linux-modules-extra-raspi
state: present
check_mode: true
register: check_raspi_module
- name: install linux-modules-extra-raspi if not installed # Task 2
shell: |
sudo dpkg --configure -a
apt install linux-modules-extra-raspi
when: not check_raspi_module.changed
But the problem here is that if I have a set of hosts, the Task 1 runs for node n1 and registeres check_raspi_module to false and then Task 1 runs for node n2 and then sets it to true because that package is already available in node n2. So how can I throttle this and have the check_raspi_module local to a task and not global like it is now?

If you need to install package, you have just to use the first bloc like below. You haven't need to use block of check and install separatly.
Even if your package is installed, Ansible will detect it and not reinstall it. It’s the principe of Ansible
The documentation: here
(definition) state: present (mean install if not present)
- name: install if not present if linux-modules-extra-raspi
ansible.builtin.package:
name: linux-modules-extra-raspi
state: present

Related

How can I run local-exec provisionner AFTER cloud-init / user_data?

I'm experiencing a race condition issue on Terraform when running an Ansible playbook with the local-exec provisioner. At one point, that playbook has to install an APT package.
But first, I'm running a cloud-config file init.yml specified in the user_data argument that installs a package as well.
Consequently, I'm getting the following error :
Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?
How can I prevent this?
# init.yml
runcmd:
- sudo apt-get update
- sudo apt-get -y install python python3
# main.tf
resource "digitalocean_droplet" "hotdog" {
image = "ubuntu-18-04-x64"
name = "my_droplet"
region = "FRA1"
size = "s-1vcpu-1gb"
user_data = file("init.yml")
provisioner "local-exec" {
command = "ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i '${self.ipv4_address},' ./playbook.yml"
}
}
Disclaimer: my terraform knowledge is quite sparse compared to my ansible one. The below should work but there might be terraform centric options I totally missed
A very easy solution is to use an until loop so as to retry the task until it succeeds.
- name: retry apt task every 5s during 1mn until it succeeds (e.g. lock is released)
apt:
name: my_package
register: apt_install
until: apt_install is success
delay: 5
retries: 12
A better approach would be to make sure there is no lock in place on the different dpkg lock files. I did not make the exercise to implement this in ansible and you might need a specific script or custom module to succeed. If you want to give it a try, there is a question with a solution on serverfault
In your context and since this problem seems quite common actually, I searched a bit and I came across this issue on github. I think that my adaptation as below will meet your requirements and might help for any other possible race condition inside your init phase.
Modify your user_data as:
---
runcmd:
- touch /tmp/user-init-running
- sudo apt-get update
- sudo apt-get -y install python python3
- rm /tmp/user-init-running
And in your playbook:
- name: wait for init phase to end. Error after 1mn
wait_for:
path: /tmp/user-init-running
state: absent
delay: 60
- name: install package
apt:
name: mypkg
state: present

package installation not considered in playbook

I got some trouble with automating an installation using ansible.
I use this role (https://github.com/elastic/ansible-elasticsearch) to install elasticsearch on my ubuntu 16.04 server.
The role depends on the package python-jmespath, as mentioned in the documentation.
The role DOES NOT install the package itsself, so i try to install it before role execution.
- hosts: elasticsearch_master_servers
become: yes
tasks:
- name: preinstall jmespath
command: "apt-get install python-jmespath"
- name: Run the equivalent of "apt-get update" as a separate step
apt:
update_cache: yes
- hosts: elasticsearch_master_servers
become: yes
roles:
- role: elastic.elasticsearch
vars:
...
When running the playbook i expect the python-jmespath package to be installed before execuction of role takes place, but role execution fails with
You need to install \"jmespath\" prior to running json_query filter"
When i check if the package is installed manually using dpkg -s python-jmespath i can see the package is installed correctly.
A second run of the playbook (with the package already installed) doesnt fail.
Do I miss an ansible configuration, that updates the list of installed packages during the playbook run ?
Am I doing something wrong in general ?
Thanks in advance
FWIW. It's possible to tag installation tasks and install the packages in the first step. For example
- name: install packages
package:
name: "{{ item.name }}"
state: "{{ item.state|default('present') }}"
state: present
loop: "{{ packages_needed_by_this_role }}"
tags: manage_packages
Install packages first
shell> ansible_playbook my-playbook.yml -t manage_packages
and then run the playbook
shell> ansible_playbook my-playbook.yml
Notes
This approach makes checking of the playbooks with "--check" much easier.
Checking idempotency is also easier.
With tags: [manage_packages, never] the package task will be skipped when not explicitly selected. This will speed up the playbook.

Ansible yum disablerepo does not work if repo is not installed

I have a number of different Centos7 servers running. I like to use ansible to update them all at once.
As one of my servers has an additional repository enabled, which I do not want to update. I've added to the playbook the option to disable this repo. This works as expected.
However, on my other servers, I did not install and enable this repo. When using the disablerepo in my ansible playbook, I get an error: repository not found.
How do I solve this in the ansible-playbook? Is it possible to add an condition like, if repo installed; then disablerepo; else do nothing?
Is it possible to ignore these errors?
ansible-playbook:
---
- hosts: [all]
tasks:
- name: update all packages to lastest version
yum:
name: '*'
state: latest
disablerepo: sernet-samba-4.2
you can put ignore_errors: yes as in the link from the comment, or you can put when, only if certain package is installed, sure you have to register them to variables first, I'm thinking something like:
- name: check if installed
shell: rpm -qa sernet-samba-4.2
register: is_installed
- name: update all packages to lastest version
yum:
name: '*'
state: latest
disablerepo: sernet-samba-4.2
when: is_installed.rc == 1
Warning: Untested.
After a day of research in internet and experiments finally found a solution that worked. Try to use wildcard.. then it will not fail when repo is missing.
yum:
name: ''
state: latest
disablerepo: sernet-samba

Ansible Yum Module pending transactions error

I'm very new to Ansible.
I am trying to follow a tutorial on the concept of Roles in Ansible.
I have the following Master Playbook:
--- # Master Playbook for Webservers
- hosts: apacheweb
user: test
sudo: yes
connection: ssh
roles:
- webservers
Which refers to the webservers role that has the following task/main.yml:
- name: Install Apache Web Server
yum: pkg=httpd state=latest
notify: Restart HTTPD
And a handler/main.yml:
- name: Restart HTTPD
service: name=httpd state=started
When I execute the Master Playbook, mentioned above, I get the following error:
TASK [webservers : Install Apache Web Server] **********************************
fatal: [test.server.com]: FAILED! => {"changed": false, "failed": true, "msg": "The following packages have pending transactions: httpd-x86_64", "rc": 128, "results": ["The following packages have pending transactions: httpd-x86_64"]}
I cannot understand what this error corresponds to. There does not seem to be anything similar, based on my research, that could suggest the issue with the way I am using the Yum module.
NOTE: Ansible Version:
ansible 2.2.1.0
config file = /etc/ansible/ansible.cfg
It seems there are unfinished / pending transactions on the target host.
Try installing yum-utils package to run yum-complete-transaction to the target hosts giving the error.
# yum-complete-transaction --cleanup-only
Look at Fixing There are unfinished transactions remaining for more details.
yum-complete-transaction is a program which finds incomplete or
aborted yum transactions on a system and attempts to complete them. It
looks at the transaction-all* and transaction-done* files which can
normally be found in /var/lib/yum if a yum transaction aborted in the
middle of execution.
If it finds more than one unfinished transaction it will attempt to
complete the most recent one first. You can run it more than once to
clean up all unfinished transactions.
Unfinished transaction remaining
sudo yum install yum-utils
yum-complete-transaction --cleanup-only
I am using for ansible this type of config for the playbooks:
- name: Install Apache Web Server
yum: name=httpd state=latest
notify: Restart HTTPD
As far as i know there is no such option as yum: pkg=httpd in ansbile for the yum module (if I'm not wrong, that pkg=httpd is for apt-get on debian based distros)
If you need to install multiple packages you could use something like:
- name: "Install httpd packages"
yum: name={{ item }} state=present
with_items:
- httpd
- httpd-devel
- httpd-tools
Of course you can change the state=present to state=latest or whatever option might suits you best
http://docs.ansible.com/ansible/yum_module.html - ansible documentation for yum module

How can I get the installed YUM packages with Ansible?

I am trying to get all the installed YUM packages on an RHEL machine. I can easily get it through using shell commands which is not idempotent and would like to use the YUM command instead.
The shell command works fine:
- name: yum list packages
shell: yum list installed > build_server_info.config
But when I try to use the YUM command, it just executes, but it does not give any results:
- name: yum_command
action: yum list=${pkg} list=available
I can easily get it through using shell commands which is not idempotent
You can't really talk about idempotence, when you are querying the current state of a machine.
"Idempontent" means that the task will ensure the machine is in the desired state no matter how many times you run a certain task.
When you query current state, you don't describe the desired state. No matter what you do, what method you use, the term "idempotent" is just not applicable.
Regarding your example, which does not give you results - you have repeated twice the same argument list and the task should fail (it doesn't, which looks like an Ansible quirk).
To get a list of installed packages, you should use:
- name: yum_command
yum:
list=installed
register: yum_packages
- debug:
var: yum_packages
It saves a list of dictionaries describing each package to a variable yum_packages.
You can then use a JSON Query Filter to get a single package (tar):
- debug: var=item
with_items: "{{yum_packages|json_query(jsonquery)}}"
vars:
jsonquery: "results[?name=='tar']"
to get a result like this:
"item": {
"arch": "x86_64",
"epoch": "2",
"name": "tar",
"nevra": "2:tar-1.26-31.el7.x86_64",
"release": "31.el7",
"repo": "installed",
"version": "1.26",
"yumstate": "installed"
}
Or only its version:
- debug: var=item
with_items: "{{yum_packages|json_query(jsonquery)}}"
vars:
jsonquery: "results[?name=='tar'].version"
"item": "1.26"
Since Ansible 2.5, you can also use the package_facts module: it will gather the list of installed packages as Ansible facts.
Example from the CPU:
- name: get the rpm package facts
package_facts:
manager: rpm
- name: show them
debug: var=ansible_facts.packages
Well, the official Ansible documentation for the yum module describes list as:
"Various (non-idempotent) commands for usage with /usr/bin/ansible and not playbooks."
so you're going to be out of luck with finding an idempotent list invocation.
If you just want to suppress the changed output, set the changed_when parameter to False.
(Also, having the duplicate list parameter is suspicious.)
If your requirement is to check for only one package, and based on that you want to execute another task, you can use package_facts module as
- name: install docker community edition in rhel8
hosts: localhost
connection: local
tasks:
- name: Gather the rpm package facts
package_facts:
manager: auto
- name: check if docker-ce is already installed
debug:
var: ansible_facts.packages['docker-ce']
- name: using command module to install docker-ce
command: yum install docker-ce --nobest -y
when: "'docker-ce' not in ansible_facts.packages"
register: install_res
- name: whether docker is installed now
debug:
var: install_res

Resources