latest version of a package in a Content View Version

Latest response

I realize this is not a trivial request, but given this command

hammer -d package list --search 'name=kernel' --content-view-version-id 632 --order 'release ASC'
....
[ INFO 2018-02-21 15:22:39 API] GET /katello/api/packages
[DEBUG 2018-02-21 15:22:39 API] Params: {
            "organization_id" => "1",
    "content_view_version_id" => 632,
                     "search" => "name=kernel",
                       "page" => 1,
                   "per_page" => 1000,
                      "order" => "release ASC",
                       "sort" => {
        "order" => "release ASC"
    }
......
-------|-----------------------------------------
ID     | FILENAME                                
-------|-----------------------------------------
146017 | kernel-3.10.0-229.11.1.ael7b.ppc64le.rpm
147901 | kernel-3.10.0-229.1.2.ael7b.ppc64le.rpm 
149129 | kernel-3.10.0-229.14.1.ael7b.ppc64le.rpm
150104 | kernel-3.10.0-229.20.1.ael7b.ppc64le.rpm
147371 | kernel-3.10.0-229.4.2.ael7b.ppc64le.rpm 
148292 | kernel-3.10.0-229.7.2.ael7b.ppc64le.rpm 
148690 | kernel-3.10.0-229.ael7b.ppc64le.rpm     
145498 | kernel-3.10.0-327.10.1.el7.ppc64le.rpm  
147019 | kernel-3.10.0-327.13.1.el7.ppc64le.rpm  
147415 | kernel-3.10.0-327.18.2.el7.ppc64le.rpm  
149251 | kernel-3.10.0-327.22.2.el7.ppc64le.rpm  
150094 | kernel-3.10.0-327.28.2.el7.ppc64le.rpm  
149353 | kernel-3.10.0-327.28.3.el7.ppc64le.rpm  
146098 | kernel-3.10.0-327.3.1.el7.ppc64le.rpm   
145665 | kernel-3.10.0-327.36.1.el7.ppc64le.rpm  
149855 | kernel-3.10.0-327.36.2.el7.ppc64le.rpm  
150268 | kernel-3.10.0-327.36.3.el7.ppc64le.rpm  
151277 | kernel-3.10.0-327.4.4.el7.ppc64le.rpm   
146872 | kernel-3.10.0-327.4.5.el7.ppc64le.rpm   
147006 | kernel-3.10.0-327.el7.ppc64le.rpm       
183723 | kernel-3.10.0-514.10.2.el7.ppc64le.rpm  
185594 | kernel-3.10.0-514.16.1.el7.ppc64le.rpm  
190482 | kernel-3.10.0-514.21.1.el7.ppc64le.rpm  
190873 | kernel-3.10.0-514.21.2.el7.ppc64le.rpm  
149090 | kernel-3.10.0-514.2.2.el7.ppc64le.rpm   
191032 | kernel-3.10.0-514.26.1.el7.ppc64le.rpm  
195988 | kernel-3.10.0-514.26.2.el7.ppc64le.rpm  
167408 | kernel-3.10.0-514.6.1.el7.ppc64le.rpm   
169620 | kernel-3.10.0-514.6.2.el7.ppc64le.rpm   
143736 | kernel-3.10.0-514.el7.ppc64le.rpm       
214287 | kernel-3.10.0-693.11.1.el7.ppc64le.rpm  
222287 | kernel-3.10.0-693.11.6.el7.ppc64le.rpm  
205258 | kernel-3.10.0-693.1.1.el7.ppc64le.rpm   
224547 | kernel-3.10.0-693.17.1.el7.ppc64le.rpm  
208060 | kernel-3.10.0-693.2.1.el7.ppc64le.rpm   
208774 | kernel-3.10.0-693.2.2.el7.ppc64le.rpm   
210822 | kernel-3.10.0-693.5.2.el7.ppc64le.rpm   
200544 | kernel-3.10.0-693.el7.ppc64le.rpm       
-------|-----------------------------------------

One would expect a properly ordered package list. I've been mucking with this today in API form and hammer form and can't come up with a good way to get the latest version of a specific package available in any given content view version

Any ideas ?

Responses

Your best best probably will be to get the output from the API and write a function to sort the RPMs taking into account Name, Version, Release, Epoch, and Architecture. The API will just return them as a list, sorted lexically.

I had a feeling you would say that. Some days I really miss the simplicity of spacewalk https://spacewalkproject.github.io/documentation/api/2.7/handlers/SystemHandler.html#listLatestAvailablePackage https://spacewalkproject.github.io/documentation/api/2.7/handlers/SystemHandler.html#listLatestInstallablePackages https://spacewalkproject.github.io/documentation/api/2.7/handlers/SystemHandler.html#listLatestUpgradablePackages https://spacewalkproject.github.io/documentation/api/2.7/handlers/SystemHandler.html#listNewerInstalledPackages

Anyone think its worth an RFE for this to implement in Satellite 6?

I do feel it is a sane RFE. Sorts should be 'natural' to the type of data. I'd expect hostnames to be sorted case-insensitively (because DNS is case-insensitive). RPM sorting should be the same.

As usual it didn't take long, once I put my head into it. Using the python rpm module helps out a bunch, but pro grammatically you have to work through the versions and sort as Rich mentioned. Stack Exchange

[
    {
        "host_id": 563,
        "update_needed": true,
        "host_name": "hosta.example.net",
        "latest_kernel": "kernel-2.6.32-696.20.1.el6.x86_64",
        "running_kernel": "2.6.32-696.16.1.el6.x86_64"
    },
    {
        "host_id": 558,
        "update_needed": true,
        "host_name": "hostb.example.nett",
        "latest_kernel": "kernel-2.6.32-696.20.1.el6.x86_64",
        "running_kernel": "2.6.32-696.16.1.el6.x86_64"
    },
    {
        "host_id": 547,
        "update_needed": true,
        "host_name": "hostc.example.net",
        "latest_kernel": "kernel-2.6.32-696.20.1.el6.x86_64",
        "running_kernel": "2.6.32-696.16.1.el6.x86_64"
    },
    {
        "host_id": 467,
        "update_needed": false,
        "host_name": "hostd.example.net",
        "latest_kernel": "kernel-3.10.0-693.17.1.el7.x86_64",
        "running_kernel": "3.10.0-693.17.1.el7.x86_64"
    }
]

In case anyone else is looking for a solution maybe this'll help.. I'm not a programmer so no butchering:D Also it include a Content View list{dictionary} that gets updated everytime a new CV is queried, in order to make the lookups faster on subsequent hosts using that Content View

import re
import rpm
import argparse

#Work on the command line args
parser = argparse.ArgumentParser()
# Optional Arguments
parser.add_argument("-c", "--collection" ,help="Provide a Host Collection to search",required=True)
args = parser.parse_args()


def get_hosts():
  hosts = get_json(SAT_API + "hosts?per_page=3000")
  return hosts

def get_host_name(host_id,hostname_dict):
  for item in hostname_dict:
    if item['id'] == host_id:
      return item['hostname']

def get_hc_id(hc):
  hc_id = get_json(KAT_API_URL + "host_collections?search=name%3D" + hc)
  if len(hc_id['results']) == 1:
    for item in hc_id['results']:
      hc_id = item['id']

  return hc_id

def get_hc_members(hc_id):
  hcmembers = []
  hc_members = get_json(KAT_API + "/host_collections/" + str(hc_id))
  return hc_members['host_ids']

# Get the hosts running kernel
def get_host_info(host_id):
  url = SAT_API + "/hosts/" + str(host_id)
  results = get_json(url)
  if results:
    return results

def get_host_facts(host_info):
  return host_info['facts']

def get_running_kernel(host_facts):
  return host_facts['uname::release']

def get_kernel_packages(host_id):
  kernels = []
  url = SAT_API + "/hosts/" + str(host_id) + "/packages?search=name%3Dkernel"
  packages = get_json(url)
  if packages['results']:
    return packages['results']

def get_content_view_highest_version(host_info, latest_kernels,running_kernel):
  # Check the latest_kernels and see if we have it for this content_view_version_id already, if not, then look it up
  if any(d['cvid'] == host_info['content_facet_attributes']['content_view_version_id'] for d in latest_kernels):
    for entry in latest_kernels:
      if entry['cvid'] == host_info['content_facet_attributes']['content_view_version_id']:
        return entry
  else:
    latest_kernel = {}
    kernels = []
    url = KAT_API + "/packages/?content_view_version_id=" + str(host_info['content_facet_attributes']['content_view_version_id']) + "&search=name=kernel&per_page=1000"
    results = get_json(url)
    running_kernel_version = running_kernel.split("-",1)[0]
    running_kernel_release = running_kernel.split("-",1)[1].split(".x86_64",1)[0]
    if results['results']:
      for kernel in results['results']:
        #print json.dumps(kernel,indent=4)
        ret = rpm.labelCompare(('1',kernel['version'],kernel['release']),('1',running_kernel_version,running_kernel_release))
        if ret == 1 or ret == 0:
          running_kernel_version = kernel['version']
          running_kernel_release = kernel['release']
          latest_kernel = {"id":kernel['id'],"release":kernel['release'],"version":kernel['version'],"nvra":kernel['nvra'],"cvid":host_info['content_facet_attributes']['content_view_version_id']}
      if not latest_kernel:
          latest_kernel = {"id":"","release":running_kernel_release,"version":running_kernel_version,"nvra":running_kernel,"cvid":host_info['content_facet_attributes']['content_view_version_id']}
      return latest_kernel

def compare_cv_running_kernel(cv_version,cv_release,running_kernel):
    running_kernel_version = running_kernel.split("-",1)[0]
    running_kernel_release = running_kernel.split("-",1)[1].split(".x86_64",1)[0]
    return rpm.labelCompare(('1',cv_version,cv_release),('1',running_kernel_version,running_kernel_release))

content_view_latest_kernels = []
collection_info = []

# Get the Host Collection ID
hc_id = get_hc_id(args.collection)

# Get the host collection members
host_ids = get_hc_members(hc_id)

for host_id in host_ids:
  # Grab the host information from Satellite
  host_info = get_host_info(host_id)

  # Get the host facts from the host info
  host_facts = get_host_facts(host_info)

  # Get the currently running kernel from the host_facts
  running_kernel = get_running_kernel(host_facts)

  # Get the currently installed kernel packages
  kernel_packages = get_kernel_packages(host_id)


  # Get the content view highest release version so we can calculate the latest available kernel
  cv_latest_info = get_content_view_highest_version(host_info,content_view_latest_kernels,running_kernel)

  # See if we need an upgrade
  ret = compare_cv_running_kernel(cv_latest_info['version'],cv_latest_info['release'],running_kernel)
  if ret == 1:
     update_needed = True
  else: 
     update_needed = False

  # Update the list of content views with latest kernels if its not there.
  if not any(d['cvid'] == cv_latest_info['cvid'] for d in content_view_latest_kernels):
    content_view_latest_kernels.append(cv_latest_info)

  collection_info.append({"host_id":host_info['id'],"host_name":host_info['name'],"running_kernel":running_kernel,"latest_kernel":cv_latest_info['nvra'],"update_needed":update_needed})

print json.dumps(collection_info,indent=4)