4

I am updating some of my ansible playbooks to run on the latest version of Fedora, that now uses dnf by default.

Since most of my playbooks should run on CentOS as well, I would like my script to run the ansible yum command when running against a CentOS machine, and run the dnf command (new in Ansible 1.9) when running against Fedora, but using the rest of the role as-is.

In other words, I would like to write a single operation that can intelligently pick the righ command (something akin to):

- name: Install zsh
  sudo: yes
  yum|dnf: name=zsh state=latest

...rather than copy-pasting the same command twice, replacing yum with dnf in one of the two, and then implementing some logic that says wich one of the two distinct roles to run:

- name: Install zsh (yum version)
  sudo: yes
  yum: name=zsh state=latest

- name: Install zsh (dnf version)
  sudo: yes
  dnf: name=zsh state=latest

Before somebody rushes to it: I know that yum in an alias of dnf on the latest Fedora, and that I could just leave the playbook as-is... My question is not about writing CentOS/Fedora compatible playbooks, my question is about picking a different command for the same parameters depending on the target environment

mac
  • 500
  • 1
  • 5
  • 15

2 Answers2

7

Ansible 2 has a generic package manager now:

http://docs.ansible.com/ansible/package_module.html

For older versions you can associate package managers via facts

- name: Install packages
  with_items: package_list
  action: "{{ ansible_pkg_mgr }} state=installed name={{ item }}"

Now all you need is some logic that sets ansible_pkg_mgr to apt or yum etc and all the when logic goes away.

Looks like Ansible are also working on doing what you want in a future module.

xddsg
  • 3,202
  • 2
  • 26
  • 33
  • 2
    Well, most of it anyway. On F22 `ansible_pkg_mgr` still returns `yum` rather than `dnf`. I suspect it's a bug. – Michael Hampton Jun 02 '15 at 18:12
  • 1
    There is also a **caveat** with this approach: It calls `ansible_pkg_mgr` for each package whereas directly calling `'ansible_pkg_mgr': name={{ item }} state=present` would only call it once. – tjanez Aug 11 '15 at 13:02
  • That link is broken. it looks like it should be https://docs.ansible.com/ansible/latest/collections/ansible/builtin/package_module.html now – Zach Smith Oct 28 '20 at 05:48
5

This is, as far as I can tell, one of the messier parts of Ansible. I've always thought that it should have a way to figure out what package manager is in use on its own, without requiring me to specify it.

In the meantime, I have lots of roles with tasks looking like this:

- name: Update OpenSSH on CentOS
  yum: name=openssh state=latest enablerepo=cr
  when: ansible_distribution == 'CentOS' and ansible_distribution_major_version|int == 7
  notify: restart sshd

- name: Update OpenSSH on RHEL
  yum: name=openssh state=latest
  when: ansible_distribution == 'RedHat' and ansible_distribution_major_version|int == 7
  notify: restart sshd

- name: Update OpenSSH on Fedora
  yum: name=openssh state=latest
  when: ansible_distribution == 'Fedora' andansible_distribution_major_version|int <= 21
  notify: restart sshd

- name: Update OpenSSH on Fedora
  dnf: name=openssh state=latest
  when: ansible_distribution == 'Fedora' andansible_distribution_major_version|int >= 22
  notify: restart sshd

- name: Update OpenSSH on Debian/Ubuntu
  apt: name=openssh-server state=latest
  when: ansible_os_family == 'Debian'
  notify: restart ssh

This gets a little unwieldy. However, it does have its advantages. If you inspect this carefully you'll notice that the CentOS version is different; it enables a repository while installing the package.

Of course there's a reason for doing it this way. On different systems you might even have different package names, so you may end up having to have different tasks for them anyway. But that's something you can shove in an OS-specific variable and then do something like:

- include_vars: common_os_{{ansible_distribution}}_{{ansible_distribution_major_version}}.yml

- name: Install minimum system administration packages
  yum: name={{item}} state=present
  with_items: common_packages_admin
  when: ansible_os_family == 'RedHat'

- name: Install minimum system administration packages
  apt: name={{item}} state=present
  with_items: common_packages_admin
  when: ansible_os_family == 'Debian'

But to extend this for dnf will require a bunch of extra when: logic, which is a lot of work for absolutely zero gain, since dnf's yum compatibility will take care of it. Here is a place where that hypothetical "automatically figure out what package manager to use" module would be useful.

But even if you had one, you'll still sometimes need to use individual package managers, because some of them have ... idiosyncrasies.

- name: Install salt-minion
  yum: name=salt-minion state=latest
  when: ansible_os_family == 'RedHat'

- name: Install salt-minion
  apt: name=salt-minion state=latest update_cache=yes
  when: ansible_os_family == 'Debian'

So, the best advice I can give you is: Wait until Ansible has a better way of dealing with package managers. Or write one...

Michael Hampton
  • 237,123
  • 42
  • 477
  • 940