Zack McCain
Zack McCain

Reputation: 43

Grabbing all *el7* packages from Ansible Package facts

We are doing an In-Place Upgrade from RHEL 7 to RHEL 8 in my organization and we've come to find out that not all RHEL 7 packages are removed after an OS Upgrade. I've been asked to report on all RHEL 7 packages leftover on the device. To start I've looked into Ansible Package facts which has a variable that looks something like:

'ansible_facts.packages:{
zlib: [
    {
        "arch": "x86_64",
        "epoch": null,
        "release": "11.el7",
        "source": "rpm",
        "version": "1.2.7"
    },
    {
        "arch": "x86_64",
        "epoch": null,
        "release": "10.el7",
        "source": "rpm",
        "version": "1.2.6"
    },
    {
        "arch": "x86_64",
        "epoch": null,
        "release": "10.el8",
        "source": "rpm",
        "version": "1.2.7"
    }
]
}
    

This occurs for a handful of packages, what I would like to do is to run a json_query against the entire dictionary of package_facts and determine what "el7" packages live on the host. That can tell me what I want to remove. Are there any suggestions on the json_query to run in order to find all packages that have an 'el7' in the release key?

Here's what I've tried so far:

vars:
  example_query1: "[?contains(release, 'el7')]"
  example_query2: "[?release == '*el7*']"
tasks:
  new_package_list: "{{ ansible_facts.packages | json_query(example_query) | list }}"

Upvotes: 1

Views: 117

Answers (3)

U880D
U880D

Reputation: 12124

Q: "Instead of just the package release information, what if I wanted the entire package information? Like the name, version, release, arch, etc. would something like that be possible for the RHEL 7 packages once they’re found? ... As in the filter is the el7 in the release, but the capture I want is the entire package information."

Based on the already given good answer, a only slightly changed minimal example playbook

---
- hosts: localhost
  become: false
  gather_facts: false

  tasks:

  - package_facts:

  - debug:
      msg: "{{ item.value }}"
    when: item.value | map(attribute='release') | select('search', redhat_release)
    with_dict: "{{ ansible_facts.packages }}"
    loop_control:
      label: "{{ item.key }}"
    vars:
      redhat_release: 'el9_2'

will output all 9.2 packages on a 9.5 system

TASK [debug] **********************
...
ok: [localhost] => (item=libcap) =>
  msg:
  - arch: x86_64
    epoch: null
    name: libcap
    release: 9.el9_2
    source: rpm
    version: '2.48'
...

In order to gain more performance one could focus next on combining these two

    when: item.value | map(attribute='release') | select('search', redhat_release)
    with_dict: "{{ ansible_facts.packages }}"

into one, generate a data structure before and instead of test when on list elements.

Similar Q&A

Upvotes: 1

phanaz
phanaz

Reputation: 1632

A quick (and dirty) variant with Ansible and Jinja2 filter techniques is as follows:

- name: Grab all packages with el7 releases
  set_fact:
    el7_packages: "{{ el7_packages | default({}) | combine(new_item | items2dict) }}"
  with_dict: "{{ ansible_facts.packages }}"
  loop_control:
    label: "{{ item.key }}"
  vars:
    el7_releases: "{{ item.value | map(attribute='release') | select('search', 'el7') }}"
    new_item:
      - key: "{{ item.key }}"
        value: "{{ el7_releases }}"
  when: el7_releases | list | length

- name: Print found el7 packages
  debug:
    var: el7_packages
  1. you iterate over each entry of the dict
    with_dict: "{{ ansible_facts.packages }}"

  2. you reduce the list of dicts to the release value and then filter the list by el7
    el7_releases: "{{ item.value | map(attribute='release') | select('search', 'el7') }}"

  3. only if a release with el7 exists, the package with its el7 releases is saved in the target dict el7_packages

    • Condition to ensure the list is not empty (means el7 release exists)
      when: el7_releases | list | length
    • Add new_item to target dict
      set_fact with el7_packages: "{{ el7_packages | default({}) | combine(new_item | items2dict) }}"
  4. the result is a dict, the keys are the package names, the values are lists of release strings containing el7

Notes:

  • loop_control with label is only for cosmetics in the terminal
  • the new entry (new_item) is prepared as an array and converted into a dict via items2dict before combining

Result:

TASK [Grab all packages with el7 releases] *********************
ok: [localhost] => (item=zlib)
ok: [localhost] => (item=other_pkg)
skipping: [localhost] => (item=an_other_tool)

TASK [Print found el7 packages] ********************************
ok: [localhost] => {
    "el7_packages": {
        "other_pkg": [
            "11.el7"
        ],
        "zlib": [
            "11.el7",
            "10.el7"
        ]
    }
}

Edit:

To get the whole dict as a result, instead of just the release string, only a minimal change of the variable assignment for el7_releases is required:

el7_releases: "{{ item.value | selectattr('release', 'search', 'el7') }}"

Code:

- name: Grab all packages with el7 releases
  set_fact:
    el7_packages: "{{ el7_packages | default({}) | combine(new_item | items2dict) }}"
  with_dict: "{{ ansible_facts.packages }}"
  loop_control:
    label: "{{ item.key }}"
  vars:
    el7_releases: "{{ item.value | selectattr('release', 'search', 'el7') }}"
    new_item:
      - key: "{{ item.key }}"
        value: "{{ el7_releases }}"
  when: el7_releases | list | length

- name: Print found el7 packages
  debug:
    var: el7_packages

Reslut:

TASK [Grab all packages with el7 releases] ****************************
ok: [localhost] => (item=zlib)
ok: [localhost] => (item=other_pkg)
skipping: [localhost] => (item=an_other_tool)

TASK [Print found el7 packages] ***************************************
ok: [localhost] => {
    "el7_packages": {
        "other_pkg": [
            {
                "arch": "x86_64",
                "epoch": null,
                "release": "11.el7",
                "source": "rpm",
                "version": "1.2.7"
            }
        ],
        "zlib": [
            {
                "arch": "x86_64",
                "epoch": null,
                "release": "11.el7",
                "source": "rpm",
                "version": "1.2.7"
            },
            {
                "arch": "x86_64",
                "epoch": null,
                "release": "10.el7",
                "source": "rpm",
                "version": "1.2.6"
            }
        ]
    }
}

example input data:

packages:
  zlib:
    - arch: "x86_64"
      epoch: null
      release: "11.el7"
      source: "rpm"
      version: "1.2.7"

    - arch: "x86_64"
      epoch: null
      release: "10.el7"
      source: "rpm"
      version: "1.2.6"

    - arch: "x86_64"
      epoch: null
      release: "10.el8"
      source: "rpm"
      version: "1.2.7"

  other_pkg:
    - arch: "x86_64"
      epoch: null
      release: "11.el7"
      source: "rpm"
      version: "1.2.7"

    - arch: "x86_64"
      epoch: null
      release: "10.el8"
      source: "rpm"
      version: "1.2.7"

  an_other_tool:
    - arch: "x86_64"
      epoch: null
      release: "10.el8"
      source: "rpm"
      version: "1.2.7"

Upvotes: 2

Vladimir Botka
Vladimir Botka

Reputation: 68189

Get the keys, select the matching items in the lists, and create the dictionary

  pkg_el7_keys: "{{ ansible_facts.packages.keys() }}"
  pkg_el7_vals: "{{ ansible_facts.packages |
                    dict2items |
                    map(attribute='value') |
                    map('selectattr', 'release', 'regex', '^.*el7$') }}"
  pkg_el7_dict: "{{ dict(pkg_el7_keys | zip(pkg_el7_vals)) }}"

gives

  pkg_el7_dict:
    zlib:
    - {arch: x86_64, epoch: null, release: 11.el7, source: rpm, version: 1.2.7}
    - {arch: x86_64, epoch: null, release: 10.el7, source: rpm, version: 1.2.6}

Example of a complete playbook for testing

- hosts: localhost

  vars:

    ansible_facts:
      packages:
        zlib:
        - {arch: x86_64, epoch: null, release: 11.el7, source: rpm, version: 1.2.7}
        - {arch: x86_64, epoch: null, release: 10.el7, source: rpm, version: 1.2.6}
        - {arch: x86_64, epoch: null, release: 10.el8, source: rpm, version: 1.2.7}

    pkg_el7_keys: "{{ ansible_facts.packages.keys() }}"
    pkg_el7_vals: "{{ ansible_facts.packages |
                      dict2items |
                      map(attribute='value') |
                      map('selectattr', 'release', 'regex', '^.*el7$') }}"
    pkg_el7_dict: "{{ dict(pkg_el7_keys | zip(pkg_el7_vals)) }}"
                                               
        
  tasks:

    - debug:
        var: pkg_el7_dict | to_yaml

Upvotes: 1

Related Questions