How to get latest package version with Ansible - ansible

I'm trying to replicate the command yum check-update package_name preferably with the Ansible yum module.
It provides an information to what version package would be updated after yum update execution (or ansible equivalent). For example:
root#host: $ yum check-update kernel
[...]
kernel.x86_64 3.10.0-1160.80.1.el7
[...]
root#host: $
I tried various combination of
- name: Xyz
ansible.builtin.yum:
list: updates
update_cache: true
But I can't limit it to a single package or pattern (like java*).
What I ended up with is ugly and slow (because of the download) workaround:
- name: Check latest available xyz version
yum:
name: xyz
state: latest
download_only: true
become: true
register: _result
- name: Register xyz version
set_fact:
latestXyz: "{{ _result.changes.updated[0][1] | regex_search('xyz-(.+).x86_64.*', '\\1') }}"
Is there any better way to achieve this?

One pretty easy way to achieve this is to run the yum module as you would to update the package, but enforce a dry run on it, with the check_mode option, and register the result of this task.
Inspecting the result, you'll realise that the module will list you the current version of the package and the version it would have been updated to, would it not have been run in dry run.
Given, for example
- yum:
name: expat
state: latest
check_mode: true
register: yum_latest_versions
Will populate the variable yum_latest_versions on a fedora:35 container with:
yum_latest_versions:
ansible_facts:
pkg_mgr: dnf
changed: true
failed: false
msg: 'Check mode: No changes made, but would have if not in check mode'
rc: 0
results:
- 'Installed: expat-2.5.0-1.fc35.x86_64'
- 'Removed: expat-2.4.9-1.fc35.x86_64'
Then to extract, you can indeed use a regex to search in the result:
- debug:
msg: >-
{{
(
yum_latest_versions.results
| map('regex_search', '^Installed: expat\-(.*)\.x86_64$', '\1')
).0.0
}}
Finally yields:
msg: 2.5.0-1.fc37

Related

How can I show a single fact in output from the ansible.builtin.package_facts in an AWX playbook

I am trying to gather what versions of nginx are running on instances in Openstack. I need to output if an instance has it or not, and if so, what version. I am failing miserably. Any help and patience would be greatly appreciated.
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: Check whether a package called nginx is installed
ansible.builtin.debug:
var: ansible_facts.packages['nginx']
msg: "Nginx version is ={{ ansible_version | type_debug }}"
when: "'nginx' in ansible_facts.packages"
register: op
Why you are printing ansible_version? You need to print nginx version
name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: Check whether a package called nginx is installed
ansible.builtin.debug:
msg: "Nginx version is ={{ ansible_facts.packages['nginx']}}"
when: "'nginx' in ansible_facts.packages"
register: op
I deleted also the var, you are not using it.

Ansible how to install a package only if same version not installed

I am trying to check the existing version of the package and run the install task if the same version is not been installed already.
Below is the code I am trying.
- name: Check for existing mono installation
command: "mono --version"
register: current_mono
ignore_errors: true
- name: Running "make install" for Mono
command: make install
args:
chdir: "{{ mono_install_dir }}"
become: yes
when: "mono_version|string not in current_mono.stdout"
First time this will fail because there won't be a stdout in current_mono var.
How to achieve this while running for the first time?
Since you are using make install you are using shell modules.
vars:
software_version: "1.2.3"
tasks:
First time this will fail ...
This is not absolutely necessary when using the following approach
- name: Check for existing version
shell:
cmd: software --version
warn: false
register: result
changed_when: false
failed_when: false
Please take note that some software packages like Java or Python are reporting his version to stderr.
- name: Show result
debug:
msg: "{{ result.stderr }}"
Now you can run your installer.
- name: Install latest version
shell:
cmd: "echo 'installing ...'"
warn: false
register: result
when: "software_version | string not in result.stderr"
- name: Show result
debug:
msg: "{{ result.stdout | default('was on latest version') }}"
You could test this sample playbook by using java or python as software.

How to override a yum module state for multiple packages using environment variable in Ansible?

This is my code currently -
- name: software
hosts: localhost
tasks:
- name: install packages
yum:
name:
- ansible
- docker
state: latest
become: yes
So when I run this I get the latest ansible and docker installed.
What I want is for the default value of state to remain latest, so if I just run the playbook the latest versions are downloaded, as it is now. However I want a way for me to override the state for one or both using environment variables(extra vars) when running my playbook from the command line.
So I can choose what version of ansible or docker to install.
Is there a way?
Although I do not think this is the best way to manage software version and that the I consider the following a bit ugly, here is a in-a-nutshell example to get you on track for your experimentation (untested, you may have to adapt a bit):
---
- name: software
hosts: localhost
vars:
ansible_raw_suffix: "-{{ ansible_yum_version | default('') }}"
ansible_suffix: "{{ ansible_yum_version is defined | ternary(ansible_raw_suffix, '') }}"
docker_raw_suffix: "-{{ docker_yum_version | default('') }}"
docker_suffix: "{{ docker_yum_version is defined | ternary(docker_raw_suffix, '') }}"
tasks:
- name: install packages
yum:
name:
- "ansible{{ ansible_suffix }}"
- "docker{{ docker_suffix }}"
state: "{{ yum_state | default('present') }}"
become: yes
With the above, you can:
install to the latest version if first time install
ansible-playbook software.yml
install a specific version of one or both softwares:
ansible-playbook software.yml -e ansible_yum_version=2.9.2 -e docker_yum_version=20.10.6
upgrade to the lastest version
ansible-playbook software.yml -e yum_state=latest
I will let you go on with this to add more features (e.g. allow downgrades) if you feel you still want to walk that path.

Getting a list of differences between list of installed packages and list of desired packages in ansible

(See update2 below for my final word on this.)
I'd like to have ansible generate a list of missing packages by comparing the installed packages with a list of desired packages. (The list of missing packages would then be passed to yum for installation.) It seems like this can be done using some combination of package_facts and the "difference" filter, but I can't come up with the right syntax.
For example, the following gives information about installed packages:
- name: get the rpm package facts
package_facts:
- name: show them
debug: var=ansible_facts.packages
and lists can be compared like this:
- name: Find difference of two lists
gather_facts: no
hosts: localhost
vars:
missing: "{{ input1|difference(input2) }}"
input1:
- john
- mary
input2:
- john
- alice
tasks:
- name: 'Print them out'
debug: var=missing
How can I plug these two techniques together to make a list that's usable later by yum?
Update
Zigarn's answer below gets me very close to what I want (with the caveat that it's "set_fact:" instead of "set_facts:" and "package:" instead of "packages:"). Here's what I'm trying now:
- name: Retrieve the installed packages
package_facts:
- name: Get only list of name of installed packages
set_fact:
installed_packages: "{{ ansible_facts.packages.keys() }}"
- name: Specify list of desired packages
set_fact:
desired_packages:
- emacs
- antiword
- gimp
- junk
- otherstuff
- name: Compute missing packages
set_fact:
missing_packages: "{{ desired_packages | difference (installed_packages) }}"
- name: Print list of missing packages
debug: var=missing_packages
- name: Print list of installed packages
debug: var=installed_packages
- name: Install missing packages
ignore_errors: yes
yum:
skip_broken: yes
name: "{{ item }}"
state: present
loop: "{{ missing_packages }}"
This does indeed try to install missing packages, with the odd exception of "emacs" which isn't currently installed, but also isn't put into the missing_packages list by ansible. This might just be something odd about this particular package, so I'm going to go ahead and mark zigarn's post as the answer but continue to look into the problem.
Regarding why I'm doing it this way, two reasons: First, I'm using a loop and a list instead of passing all the packages to yum at once because I want to avoid the case where a typo in the list of desired packages, or a named package that's not in the repositories, makes yum fail to install any of the listed packages. "skip_broken" doesn't help in these cases. Second, I'm installing a lot of packages (thousands) and yum is very slow, so whittling down the list to just the missing packages can save an hour on the time it takes to update a computer.
Update2
After experimenting with this for a while, I've decided that I'm not confident that I understand the data structure returned by ansible_facts.packages and the way "difference" interprets it, so the following is what I'm going to stick with for now.
- name: Get list of installed packages without arch
command: "rpm -q --qf '%{NAME}\n' -a"
args:
warn: false
register: installed_packages_noarch
- name: Get list of installed packages with arch
command: "rpm -q --qf '%{NAME}.%{ARCH}\n' -a"
args:
warn: false
register: installed_packages_arch
- name: Combine the lists of packages with and without arch
set_fact:
installed_packages: "{{ installed_packages_noarch.stdout_lines + installed_packages_arch.stdout_lines }}"
- name: Install missing packages
ignore_errors: yes
yum:
skip_broken: yes
state: present
name: "{{ item }}"
loop: "{{ desired_packages | difference (installed_packages) }}"
vars:
desired_packages:
- emacs
- gimp
- somethingelse
- anotherthing
Note that I've fallen back to just using rpm to get the list of installed packages, and that I actually have it generate two lists and combine them: A list of bare package names, and a list of packagename.architecture. This lets me specify things like libsomething.i686 or libother.x86_64 in the list of desired packages in cases where I want packages of multiple architectures installed.
First, you need to know that you don't need to compute yourself the list of missing packages as the package module (or platform-specific module yum, apt, ...) is idempotent and will anyway check itself the missing packages to install them.
But, if you need it for another purpose, here is how to do it.
package_facts is populating the ansible_facts.packages variables with a specific structured documented in https://docs.ansible.com/ansible/latest/collections/ansible/builtin/package_facts_module.html#returned-facts
If you want to compare it with a simple list of package names, you need to only use the keys of the package_facts. Then you can get the difference between the two lists:
- name: Retrieve the installed packages
package_facts:
- name: Get only list of name of installed packages
set_fact:
installed_packages: "{{ ansible_facts.packages.keys() }}"
- name: Compute missing packages
set_fact:
missing_packages: "{{ package_list_you_want | difference (installed_packages) }}"
- name: Install missing packages
package:
name: "{{ missing_packages }}"
state: present

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