skip to Main Content

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 }}"

3

Answers


  1. 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

    My 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"
    

    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"
            ]
        }
    }
    
    Login or Signup to reply.
  2. 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 list before and instead of test when on list elements.

    Login or Signup to reply.
  3. 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
    

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search