Install local RPM with regex or glob filename using Ansible - ansible

I am trying to write an ansible playbook which installs some RPMs for me after they have been copied to a known location by a Jenkins job. The problem is, I'm not sure how to get the name of the RPM to install without hard coding it.
Here is what I have now:
- hosts: localhost
roles:
- { role: some_role, artifacts: "{{ rpm_path }}/prefix_.*.rpm" }
In this case, rpm_path would be something like:
"/home/jenkins/workspace/rpm_install/artifacts"
The role that is called in this example handles the yum install part:
- name: Install RPMs
yum: name={{item}} state=present
with_items:
- "{{ artifacts }}"
I'd rather not have to hard code RPM names since they come from Jenkins and they are always different. But is there a way either through the yum module, or when I call the role where the regular expression or glob can be interpreted so the full path (rpm name included) is handed to yum?

You should use with_fileglob insted of with_items, something like
- name: Install RPMs
yum: name="{{item}}" state=present
with_fileglob:
- "{{ artifacts }}"

Related

ansible-playbooks - install a list of apt packages from a file

I'm trying create a Ansible playbook that will read contents of a file and use those contents to install packages on a target machine.
In simpler terms, I want to run this command converted to an ansible playbook
cat ./meta/install-list/apt | xargs apt install -y
./meta/install-list/apt
neofetch
tmux
git
./ansible/playbooks/apt.yaml
- hosts: all
become: true
tasks:
- name: Extract APT packages to install
command: cat ../../meta/install-list/apt
register: _pkgs
delegate_to: localhost
run_once: true
- name: Install APT packages
apt:
name: "{{ _pkgs.stdout_lines }}"
state: latest
./ansible.cfg
[defaults]
inventory = ./ansible/inventory/hosts.yaml
./ansible/inventory/hosts.yaml
---
all:
children:
group-machines:
hosts:
target-machine.local
Command to run playbook
ansible-playbook --ask-become-pass ./ansible/playbooks/apt.yaml --limit group-machine
When running the command, it gets stuck on Extract APT packages to install
NOTE:
these files mentioned above are to be only on machine that is running the command. If possible, I'd like to prevent copying files to target machines and then running the playbooks tasks
PS: new to ansible
I don't see anything in your "Extract APT packages to install" task that should cause it to get stuck... but you don't need that task in any case; you can combine your two tasks into a single task like this:
- hosts: all
become: true
tasks:
- name: Install APT packages
apt:
name: "{{ packages }}"
state: latest
vars:
packages: "{{ lookup('file', '../../meta/install-list/apt').splitlines() }}"
Here we're using a file lookup to read the contents of a file. Lookups always run on the local (control) host.
Note that you could write the above like this as well...
- hosts: all
become: true
tasks:
- name: Install APT packages
apt:
name: "{{ lookup('file', '../../meta/install-list/apt').splitlines() }}"
state: latest
...but I like to keep longer jinja expressions in vars in order to keep the rest of the arguments more readable.
The above answer is more than enough by #Zeitounator. But if you do some formatting to your original file of package list as below
packages:
- neofetch
- tmux
- git
After that you can simply run the playbook like below
- hosts: all
become: true
vars_files: ../../meta/install-list/apt
tasks:
- name: Install APT packages
apt:
name: "{{ packages }}"
state: latest
Now suppose if you are lazy enough to not want to do the formatting then below playbook also will do the trick. Its much cleaner and scalable in my opinion.
---
- name: SHow the packages list
hosts: localhost
become: true
tasks:
- name: View the packages list file
shell: cat ../../meta/install-list/apt
register: output
- name: Install the package
apt:
name: "{{ output.stdout_lines }}"
state: latest

ansible yum remove packages on first run, not on all after

I'm attempting to uninstall a list of packages from our RHEL servers. However, I need to account for servers where these packages to uninstall are needed for the application. An good example of this is httpd, which is listed on our uninstall list, but it is an dependency for the application running on the server. Basically I'm managing two states with one playbook.
So here is the list of packages to remove, which is in the role's defaults/main.yml
packagesRemove:
- telnet
- nfs
- nfs-server
- nfs-utils
- named
- httpd
- rsync
- postfix
- autofs
- cups
- smb
- squid
Currently, I'm doing something basic to uninstall the packages on the first run.
- name: Check for packageRemove file
stat:
path: /root/packageRemove.txt
register: stat_result
- name: remove packages not needed
yum:
name: "{{ packagesRemove }}"
autoremove: yes
register: packageRemove_output
when: not stat_result.stat.exists
- name: create packageRemove file
template:
src: output.txt.j2
dest: /root/packageRemove.txt
owner: root
group: root
mode: 0600
when: not stat_result.stat.exists
Basically if the /root/packageRemove.txt file exits, these tasks just get skipped. How can I make this more dynamic, and remove the need for the /root/packageRemove.txt file. I would like to make the packages that are needed into some sort of inventory variables.
Right now, I just have the following to gather a list of packages installed on the server.
- name: gather installed packages
dnf:
list: installed
no_log: true
register: yum_packages
- name: make installed packages a list
set_fact:
installed_packages: "{{ yum_packages.results | map(attribute='name') | list }}"
This is now where I'm stumped, and I'm not quite sure what my next step should be or if I'm on the right track. Any help would be great.

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

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.

Resources