How to solve all AIX package installation issues with one Ansible role
Let's continue reinventing the package installation
I wrote about problems with DNF and Ansible on AIX. I wrote about issues with BFF/LPP installations with Ansible on AIX. It's time to resolve the issues. Of course, using Ansible. I know, it would be better to have something universal, artificial intelligence, that can decide for us what should be used and how. Unfortunately for you, my intelligence is not artificial, but rather quite old-school human. Let’s see what we can achieve with human intelligence and Ansible.
The plan
I want to pass only one parameter to my role: the package name to install.
The role must decide which LPP source to use, mount it if executed on a NIM client, install the package, and then unmount it. If the role is executed on the NIM server, it must install the package from the LPP source directory.
Of course, I must be able to specify the LPP source to use if I want to use a specific LPP source.
Start!
Let’s start creating the role.
Define defaults
The role takes two parameters: package name and LPP source. We defined them in defaults/main.yml file.
---
# package: (str) name of the package to install
package: bos.mp64
# lpp_source: (str) lpp source on NIM to search for the package.
# the default value is set to AIX oslevel
lpp_source: ''
Why do I use bos.mp64 as the default value for the package name? Because if I forget to specify a package name, the role will not abort. It will do nothing. This is what I wish from the role. If you want another behaviour, change the default value.
Set default LPP source
The rest is done in tasks/main.yml file. Because everything else is tasks.
We start with setting the LPP source name, if none was provided by the role’s user.
- name: Set default lpp_source if it is not set
when: lpp_source == ''
block:
- name: Get AIX level
ansible.builtin.command:
cmd: oslevel -s
changed_when: false
register: oslevel
- name: Set lpp_source
ansible.builtin.set_fact:
lpp_source: "{{ oslevel.stdout }}-lpp_source"
As you see, I get the full AIX version using the oslevel -s
command, and then use it to determine the lpp_source name. I can do it because all my LPP sources are defined according to the oslevel
command output. If you have another logic, you must modify these lines according to your logic.
Determine if the role runs on the NIM server
The next part is to determine where the role runs. We can do it by checking the nimesis service. The service runs only on a NIM server, unless you have some strange configuration.
- name: Check where we run
block:
- name: Check if nimesis service is running
ansible.builtin.command:
cmd: lssrc -s nimesis
changed_when: false
no_log: true
- name: Set NIM master environment
ansible.builtin.set_fact:
nim_env: master
rescue:
- name: Set NIM client environment
ansible.builtin.set_fact:
nim_env: client
To distinguish between the two cases, I set the variable nim_env. It has the value ‘master’ if the playbook runs on the NIM master and the value ‘client’ if it runs on a NIM client.
Different task files for environments
To make it easier for me, I create two different files - one for the NIM master and another for the NIM clients. You can do everything in one file, if you wish, just add many “when” conditions.
- name: Include tasks
ansible.builtin.include_tasks:
file: "nim_{{ nim_env }}.yml"
NIM master tasks
I start with the NIM master tasks because they are easy to implement.
We must first verify that the LPP source actually exists. Remember, we can pass a parameter to the role with an LPP source name. We can make a minor typo, and the LPP source is wrong then. We check for it:
- name: Check if lpp_source exists
ansible.builtin.command:
cmd: "lsnim {{ lpp_source }}"
changed_when: false
failed_when: false
register: lsnim_lpp
- name: Abort if lpp_source doesn't exist
ansible.builtin.fail:
msg: "LPP source {{ lpp_source }} was not found"
when: lsnim_lpp.rc != 0
Next, we get the location of the LPP source. I use the ansible.builtin.shell module and awk to get it. I think this is the best way to get the location. If you know a better way, please let me know as well.
- name: Get directory of the lpp_source
ansible.builtin.shell:
cmd: "lsnim -l {{ lpp_source }} | awk -F= '/location/ { print $NF }'"
changed_when: false
register: lpp_location
Now we are ready to install packages. Let’s do it!
- name: Install package
ibm.power_aix.installp:
install_list: "{{ package }}"
device: "{{ lpp_location.stdout_lines.0 | trim }}/installp/ppc"
agree_licenses: true
dependencies: true
action: apply
Of course, you can change any parameters in the task, or define them as variables or defaults to be passed into the role.
NIM client tasks
We repeat the same steps as for the NIM master. The difference is that we use different commands with different syntax.
Similar to the NIM master tasks, we first check if the LPP source exists.
- name: Check if lpp_source exists
ansible.builtin.command:
cmd: "nimclient -l {{ lpp_source }}"
changed_when: false
failed_when: false
register: lsnim_lpp
- name: Abort if lpp_source doesn't exist
ansible.builtin.fail:
msg: "LPP source {{ lpp_source }} was not found"
when: lsnim_lpp.rc != 0
We must get the LPP source’s location.
- name: Get directory of the lpp_source
ansible.builtin.shell:
cmd: "nimclient -l -l {{ lpp_source }} | awk -F= '/location/ { print $NF }'"
changed_when: false
register: lpp_location
Additionally, we must get NIM master’s name.
- name: Get NIM master name
ansible.builtin.command:
cmd: awk -F= '/NIM_MASTER_HOSTNAME/ {print $NF}' /etc/niminfo
changed_when: false
register: nim_master
Our next step is to allocate the LPP source to our client.
- name: Allocate the NIM resource
ansible.builtin.command:
cmd: "nimclient -o allocate -a lpp_source={{ lpp_source }}"
changed_when: false
Now, we create a temporary directory to mount the LPP source.
- name: Create temporary directory
ansible.builtin.tempfile:
path: /tmp
prefix: lpp.
suffix: .mnt
state: directory
register: tmpdir
We are ready to mount the LPP source and install the package. We should do it in a block. If you wish, you can do it without the block. In the event of an error, you will be left with an unnecessary temporary directory and an NFS mount.
- name: Mount lpp_source and install packages
block:
- name: Mount lpp_source
ibm.power_aix.mount:
node: "{{ nim_master.stdout_lines.0 }}"
mount_dir: "{{ lpp_location.stdout_lines.0 | trim }}"
mount_over_dir: "{{ tmpdir.path }}"
state: mount
- name: Install package
ibm.power_aix.installp:
install_list: "{{ package }}"
device: "{{ tmpdir.path }}/installp/ppc"
agree_licenses: true
dependencies: true
action: apply
Now we can clean up. The cleanup will be executed in both cases, regardless of whether the previous tasks in the block were successful or unsuccessful.
always:
- name: Unmount lpp_source
ansible.posix.mount:
path: "{{ tmpdir.path }}"
state: unmounted
fstab: /dev/null
- name: Delete temporary directory
ansible.builtin.file:
path: "{{ tmpdir.path }}"
state: absent
- name: Deallocate the NIM resource
ansible.builtin.command:
cmd: "nimclient -o deallocate -a lpp_source={{ lpp_source }}"
changed_when: false
Support the Power DevOps Newsletter!
If you like reading technical articles about IBM Power, AIX, and Linux on IBM Power, consider upgrading to the paid tier to show your support. As a paid subscriber, you not only get regular posts, but you will get additional posts with the full code and further explanations, access to the whole archive of the blog, and take part in our monthly calls where you can ask your questions and propose topics for future newsletters. Be an active member of our community!
Usage of the role
It is elementary. Just add include_role to your playbook and specify a package to install:
Of course, you can specify everything that the ibm.power_aix.installp module understands as “package”.
Your next step may be to combine both dnf and installp roles into one larger role, “install_package”, which executes either dnf if you want to install an RPM package or installp if you want to install from an LPP source. This would make playbooks even more readable.
Have fun installing packages on AIX!
Andrey
Hi, I am Andrey Klyachkin, IBM Champion and IBM AIX Community Advocate. This means I don’t work for IBM. Over the last twenty years, I have worked with many different IBM Power customers all over the world, both on-premise and in the cloud. I specialize in automating IBM Power infrastructures, making them even more robust and agile. I co-authored several IBM Redbooks and IBM Power certifications. I am an active Red Hat Certified Engineer and Instructor.
Follow me on LinkedIn, Twitter and YouTube.
You can meet me at events like IBM TechXchange, the Common Europe Congress, and GSE Germany’s IBM Power Working Group sessions.