Ansible: Playbook to install packages using yum and dnf - ansible

I'm having problem executing an Ansible playbook to install a package using yum on RHEL7 and dnf on RHEL8.
I'm using a condition as shown on my playbook below, but keep getting errors.
Error
{"msg": "The conditional check 'ansible_os_family == \"RedHat\" and ansible_lsb.major_release|int == \"7\"' failed. The error was: error while evaluating conditional (ansible_os_family == \"RedHat\" and ansible_lsb.major_release|int == \"7\"): 'dict object' has no attribute 'major_release'\n\nThe error appears to be in '/ansible/master/intall.pkg.yml': line 9, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n # (Task-1) Checks if ansible_os_family == \"RHEL7\" and then Installs telnet on Remote Node\n - name: Install telnet on RHEL7 Server\n ^ here\n"}
Playbook
---
- hosts: all
gather_facts: true
become: yes
#become_user: ansible
become_method: sudo
tasks:
# (Task-1) Checks if ansible_os_family == "RHEL7" and then Installs telnet on Remote Node
- name: Install telnet on RHEL7 Server
yum: name=telnet state=present
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int == "7"
# (Task-2) Checks if ansible_os_family == "RHEL8" and then Installs telnet on Remote Node
- name: Install telnet on RHEL8 Server
package: name=telnet state=present
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int == "8"
How do I use my playbook to skip the RHEL7 and install the package on RHEL8 using dnf?
Thank you.

As already mentioned within the comments by #Zeitounator you may have a look into Conditionals based on ansible_facts since the fact respective variable is a string.
The following minimal example is working in an production environment.
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
yum:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '7'
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
yum:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '8'
This is possible because of Software management tools in RHEL 8
Although YUM v4 used in RHEL 8 is based on DNF, it is compatible with YUM v3 used in RHEL 7. For software installation, the yum command and most of its options work the same way in RHEL 8 as they did in RHEL 7.
This means that in your specific case by using the yum or package module
This modules manages packages on a target without specifying a package manager module (like ansible.builtin.yum, ansible.builtin.apt, …). It is convenient to use in an heterogeneous environment of machines without having to create a specific task for each package manager. package calls behind the module for the package manager used by the operating system discovered by the module ansible.builtin.setup.
there would be no need for a conditional check unless there would be differences in the availability of packages and or naming.
A single task like
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
package: # or even yum
name: telnet
state: present
should just work.
Depending on your infrastructure and to be prepared for future releases you could switch to the dnf module for RHEL-8 and RHEL-9 instances.
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
dnf:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '8'

Related

How to show failure message for a loop with when in ansible?

---
- name: install httpd and mod_ssl
hosts: all
vars_files:
- vars/package_list.yaml
tasks:
- name: install httpd,firewalld and mod_ssl
yum:
name: "{{ item }}"
state: latest
loop: "{{ package_list }}"
when: >
( ansible_facts['distribution'] == 'Ubuntu' )
or
( ansible_facts['distribution'] == 'CentOS' and ansible_facts['distribution'] == 'Fedora' and ansible_facts['distribution_version'] is version( '8' , '>=') )
# some other tasks.
How can I send a failure message if task install httpd, firewalld and mod_ssl fails based on the condition that if distribution is ubuntu then proceed with the install or if the distribution is 'CenOS' ( Not 'Fedora' ) with version >= '8' then proceed with the install or show a failure message.
If the condition is not met then it exits with the failure message or else it continues with the rest of the play.
something like:
if (
( distro == 'Ubuntu' ) or
( distro == 'CentOS' and distro != 'Fedora' and version >= 8)
)
then install httpd, firewalld, mod_ssl and proceed further
else
print "Error: requirements not met for setup"
Simplify the pattern:
fail early if your target does not meet the requirement
go on with the rest of your tasks if it does
Note: I'm not sure I fully understand your condition which differs in your example playbook and pseudo code and is not really clear in both cases. So this is just a layout example. Put whatever condition is needed.
Moreover:
although it is possible, using yum on Ubuntu looks a bit awkward.
looping over yum is discouraged. You should pass the list of packages directly in the name attribute (fixed below)
---
- name: install something on my supported targets
hosts: all
vars_files:
- vars/package_list.yml
tasks:
- name: Fail if the target isn't supported
ansible.builtin.fail:
msg: target isn't supported
when: my_target_condition_is_false
- name: install packages on supported target
ansible.builtin.yum:
name: "{{ package_list }}"
state: latest
# some other tasks.

Ansible playbook indentation issue

I am starting out w/ Ansible and am working with roles for the first time. I have a main playbook that's just supposed to distinguish between Ubuntu and CentOS when installing Docker, but for some reason I can't get it to work. Is it the indentation?
- name: Install Docker for Ubuntu and CentOS
hosts: all
remote_user: xyz
tasks:
- name: Import tasks for Ubuntu
- import_tasks: ubuntu_Docker.yml
when: ansible_facts['os_family'] == "Debian"
- name: Import tasks for CentOS
- import_tasks: centos_Docker.yml
when: ansible_facts['distribution'] == "CentOS"
The error appears to be in 'filelocation': line 9, column 6, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Import tasks for Ubuntu
^ here (points at the n)
tried many indentations, just can't get it to work. It's probably trivial, but again: New to this, and a bit confused...Thx in regards.
No hyphen(-) for import_tasks. Check the example at https://docs.ansible.com/ansible/2.4/import_tasks_module.html
---
- name: Install Docker for Ubuntu and CentOS
hosts: all
remote_user: xyz
tasks:
- name: Import tasks for Ubuntu
import_tasks: ubuntu_Docker.yml
when: ansible_facts['os_family'] == "Debian"
- name: Import tasks for CentOS
import_tasks: centos_Docker.yml
when: ansible_facts['distribution'] == "CentOS"

Ansible playbook to install different packages depending on os version

I have an Ansible playbook (using ansible 1.9.3) that sets up and updates a bunch of servers. One of those steps is to install packages (using the 'apt' plugin). Until recently all of my servers have been uniform (same version of Ubuntu server). I'm introducing a newer Ubuntu server and some of package names have changed.
Here's what my tasks file looks like:
- name: install needed packages
apt: name={{ packages }} state=present update_cache=yes
(and I have a list of packages in a vars file).
I could define a variable in my inventory file for the hosts with a different Ubuntu version.
How do I change this task to use one list of packages for my current hosts, and another list of packages for my newer hosts?
Use a when clause. Here's an example that will only run on ubuntu 12.04
when: "'{{ ansible_distribution}}' == 'Ubuntu' and '{{ ansible_distribution_release }}' == 'precise'"
You could alternatively use:
when: "'{{ ansible_distribution }}' == 'Ubuntu' and '{{ ansible_distribution_version }}': '12.04'"
Edit:
Later versions of Ansible (e.g. 2.7.1) would print a warning for this statement:
[WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: '{{ansible_distribution}}' == 'Ubuntu' and '{{ ansible_distribution_release }}' == 'bionic'
Ansible now expects another syntax for conditional statements, as stated in the docs.
For Ansible 2.5 you could try this:
when: "ansible_distribution|string == 'Ubuntu' and ansible_distribution_release|string == 'bionic'"
or even:
when:
- ansible_distribution|string == 'Ubuntu'
- ansible_distribution_release|string == 'bionic'
The latest version of the docs suggests to use access the ansible_facts like this:
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "6"
To keep your playbooks cleaner, you could do this within the roles, for example, you could have a single tasks/main.yml the one based on the OS it will include the tasks matching the condition used with when, for example, if using ansible_os_family the contents of main.yml could be something like:
---
- include_tasks: freebsd.yml
when: ansible_os_family == "FreeBSD"
tags:
- unbound
- include_tasks: debian.yml
when: ansible_os_family == "Debian"
tags:
- unbound
- include_tasks: redhat.yml
when: ansible_os_family == "RedHat"
tags:
- unbound
The advantage of this approach is that besides only having a set of task per OS, you can also tag the full tasks.
This can be done even simpler and more generic allowing re-use of the same task for multiple distributions or os:es
Simply storing the packages per distribution in a dictionary
vars file:
packages: {
bionic: [ bionic-package1, bionic-package2],
focal: [focal-package1, focal-package2]
}
playbook:
- name: Install unique packages per distro
package:
name: "{{ packages[ansible_distribution_release] }}"
state: present
Note that the change to "package" module from "apt" which allows task to run on any Linux distributions not only ones using apt as a package manager

Abort if none of previous tasks were run

I have a tasks.yml that contains something like :
- name: something on Debian wheezy
when: ansible_distribution == 'Debian' and ansible_distribution_version == '7'
- name: something on Debian jessie
when: ansible_distribution == 'Debian' and ansible_distribution_version == '8'
- name: something on Ubuntu
when: ansible_distribution == 'Ubuntu'
I want to add a task (or something) that aborts the playbook if none of those tasks were run.
Maybe "something" is installing a package, and some tasks that come after are configuring that newly installed package. If I run the playbook on a RedHat system, that package won't be installed, and I want to abort before executing the tasks that configure it.
I think I could do something like:
- fail: msg="abort!"
when: not (ansible_distribution == 'Debian' and ansible_distribution_version == '7') and not (ansible_distribution == 'Debian' and ansible_distribution_version == '8') and not ansible_distribution == 'Ubuntu'
... but that's annoying. Is there any better solution?
---
- name: test
hosts: localhost
tasks:
- name: Install pkg
debug: msg="Installed pkg"
when: "{{ ansible_distribution == 'Debian' }}"
register: debian_pkg
- name: Install pkg
debug: msg="Installed pkg"
when: "{{ ansible_distribution == 'Ubuntu' }}"
register: ubuntu_pkg
- fail: msg="Unsupported OS"
when: "debian_pkg|skipped and ubuntu_pkg|skipped"
I finally went a simpler, more "business"-accurate way. I want to install something, and I then expect something to be installed. In fact, in my infrastructure where not everything is automated (I'm not root everywhere) I sometimes can't install something, but I still want to check that it is installed (it was installed out-of-band).
So, after the tasks that install something depending on the OS, I added a task that checks if it is installed, simply by trying to run it. Something like that:
- include: install_something.yml # contains the install tasks for each OS
when: can_become_root
- name: Check that something is installed
command: something --version

Ansible register and .changed not working

I have a playbook that installs a package, and needs to run a command if the package is installed. I used register <variable> and <variable>.changed to do this, however, this doesn't seem to work in Ansible 2.0 unless I'm doing it wrong. Here's my code.
- name: install syncthing (arch)
pacman: name=syncthing state=latest
when: ansible_distribution in ['Archlinux', 'Manjaro Linux']
register: syncthing
- name: enable syncthing
command: systemctl enable syncthing#jay.service
when: syncthing.changed
What happens is the first block installs the syncthing package, and gives me the following output:
changed: [myhost]
Then the next block should execute since the previous step registered a change, but unfortunately, it doesn't:
skipping: [10.10.99.193]
I'm hoping that there's an easy solution to this, I seem to be doing this correctly from what I've read of the documentation, as well as the following post: https://raymii.org/s/tutorials/Ansible_-_Only-do-something-if-another-action-changed.html
- name: install syncthing (arch)
pacman: name=syncthing state=latest
when: ansible_distribution in ['Archlinux', 'Manjaro Linux']
register: syncthing
- name: enable syncthing
command: systemctl enable syncthing#jay.service
when: ansible_distribution in ['Archlinux', 'Manjaro Linux'] and syncthing is defined and syncthing.changed

Resources