| Server IP : 3.96.16.70 / Your IP : 216.73.216.15 Web Server : Apache System : Linux ip-172-31-26-103.ca-central-1.compute.internal 6.1.163-186.299.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 24 16:35:42 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.18 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /lib/python3.9/site-packages/dnf-plugins/ |
Upload File : |
# Copyright Amazon.com, Inc. and its affiliates. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
import os
import xml.sax
from datetime import datetime
from xml.dom import minidom
from xml.etree import ElementTree
from xml.etree.ElementTree import Element, SubElement
import dnf
import hawkey
class PackageHandler(xml.sax.handler.ContentHandler):
"""Parses <packages> element in metadata file and fetches package name and eol id"""
def __init__(self):
self.pkg_data = {}
def startElement(self, tag, attrs):
if tag == "package":
eol_id = attrs.get("note", "eol")
data = self.pkg_data.setdefault(eol_id, [])
data.append(attrs["name"])
class StatementHandler(xml.sax.handler.ContentHandler):
"""Parses <statement> element in metadata file and fetches all support statements"""
def __init__(self):
self._current = None
self._tags_stack = None
self.support_data = {}
def startElement(self, tag, attrs):
if tag == "statement":
self._current = {
"eol_id": attrs["id"],
"start_date": attrs["start_date"],
"status": attrs["marker"],
"summary": "",
"link": "",
"text": "",
}
if "end_date" in attrs:
self._current["end_date"] = attrs["end_date"]
self._tags_stack = []
if tag == "package" and self._current is not None:
note_id = attrs.get("note", self._current["eol_id"])
data = self.support_data.setdefault(note_id, {})
data.update(self._current)
if self._current is not None:
self._tags_stack.append(tag)
def characters(self, content):
if self._current is not None:
if self._tags_stack[-1] == "summary":
self._current["summary"] += content
elif self._tags_stack[-1] == "link":
self._current["link"] += content
elif self._tags_stack[-1] == "text":
self._current["text"] += content
def endElement(self, tag):
if self._current is not None:
if self._tags_stack[-1] == "statement":
self.support_data[self._current["eol_id"]] = self._current
self._current = None
self._tags_stack = None
else:
del self._tags_stack[-1]
class NoteHandler(xml.sax.handler.ContentHandler):
"""Parses <note> element in metadata file and fetches notes for namespaced packages"""
def __init__(self):
self._current = None
self._tags_stack = None
self.note_data = {}
def startElement(self, tag, attrs):
if tag == "note":
self._current = {"eol_id": attrs["id"], "pkg_info": ""}
self._tags_stack = []
if self._current is not None:
self._tags_stack.append(tag)
def characters(self, content):
if self._current is not None:
if self._tags_stack[-1] == "note":
self._current["pkg_info"] += content
def endElement(self, tag):
if self._current is not None:
if self._tags_stack[-1] == "note":
self.note_data[self._current["eol_id"]] = self._current["pkg_info"]
self._current = None
self._tags_stack = None
else:
del self._tags_stack[-1]
# available filters for packages
STATE_AVAILABLE = "available"
STATE_UNAVAILABLE = "unavailable"
STATE_INSTALLED = "installed"
SUPPORT_STATUS_SUPPORTED = "supported"
SUPPORT_STATUS_UNSUPPORTED = "unsupported"
@dnf.plugin.register_command
class SupportInfoCommand(dnf.cli.Command):
"""Provides a manual command to check support statements for any package"""
aliases = ("supportinfo",)
summary = "Get support statements for DL packages"
def prettify(self, elem):
"""Return a pretty-printed XML string for package support statements"""
rough_string = ElementTree.tostring(elem, "utf-8")
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")
def _nevra_parser(self, nevra_str):
"""Parse a full nevra string and return a nevra dict"""
subject = dnf.subject.Subject(nevra_str)
possible_nevra = list(subject.get_nevra_possibilities(forms=[hawkey.FORM_NEVRA]))
if possible_nevra:
nevra = possible_nevra[0]
else:
keys = ["name", "epoch", "version", "release", "arch"]
res = {key: "Unknown" for key in keys}
return res
nevra_dict = {
"name": nevra.name,
"epoch": nevra.epoch,
"version": nevra.version,
"release": nevra.release,
"arch": nevra.arch,
}
return nevra_dict
def get_available_installed(self):
"""Collect all available and installed packages"""
with dnf.Base() as base:
# read the configuration file
conf = base.conf
conf.read()
# set installroot
installroot = "/"
conf.installroot = installroot
# don't prompt for user confirmations
conf.assumeyes = True
# load substitutions (awsregion, awsdomain) from the filesystem
conf.substitutions.update_from_etc(installroot)
# check amazonlinux.repo (and others) to find packages available to install
base.read_all_repos()
base.fill_sack(load_system_repo=True)
# query matches all packages in sack
q = base.sack.query()
# derived query matches only latest available packages
q_available = q.available().latest().run()
# derived query matches only installed packages
q_installed = q.installed().run()
return q_available, q_installed
def _pkg_state_helper(self, pkg, state):
"""Returns installation states and other metadata per package in a dictionary"""
pkg_nevra = self._nevra_parser(str(pkg))
vr = pkg_nevra["version"] + "-" + pkg_nevra["release"]
arch = pkg_nevra["arch"]
pkg_state = {"name": pkg_nevra["name"], "state": state, "version": vr, "arch": arch}
return pkg_state
def get_packages_state(self):
available_pkgs, installed_pkgs = self.get_available_installed()
map_states = {STATE_INSTALLED: {}, STATE_AVAILABLE: {}}
for pkg in installed_pkgs:
pkg_name = self._nevra_parser(str(pkg))["name"]
map_states[STATE_INSTALLED][pkg_name] = self._pkg_state_helper(pkg, STATE_INSTALLED)
for pkg in available_pkgs:
pkg_name = self._nevra_parser(str(pkg))["name"]
map_states[STATE_AVAILABLE][pkg_name] = self._pkg_state_helper(pkg, STATE_AVAILABLE)
return map_states
def show_xml(
self, pkg, eol_id, nevra, support_status, summary, start_date, end_date, pkg_info, link
):
"""Create a basic XML template similar to support_info.xml"""
package_support = Element(
"package_support", {"current_as": datetime.now().strftime("%Y-%m-%d")}
)
statements = SubElement(package_support, "statements")
statement_attrs = {
"id": eol_id,
"marker": support_status,
"start_date": start_date,
}
if end_date:
statement_attrs["end_date"] = end_date
statement = SubElement(statements, "statement", statement_attrs)
pkg_summary = SubElement(statement, "summary")
pkg_summary.text = summary
pkg_support_info = SubElement(statement, "text")
pkg_support_info.text = pkg_info
pkg_support_link = SubElement(statement, "link")
pkg_support_link.text = link
packages = SubElement(statement, "packages")
SubElement(packages, "package", {"name": pkg, "nevra": nevra})
print(self.prettify(package_support))
def package_info_field(self, dnf_output, key, val, fill=20):
"""Format command line output"""
return dnf_output.fmtKeyValFill(dnf.i18n.fill_exact_width(key, fill) + " : ", val or "")
def show_support_periods(self, dnf_output, support_statement):
"""Format support periods output to display different support states"""
if "end_date" in support_statement:
pkg_supported_period = self.package_info_field(
dnf_output, f"from {support_statement['start_date']}", SUPPORT_STATUS_SUPPORTED
)
pkg_unsupported_period = self.package_info_field(
dnf_output, f"from {support_statement['end_date']}", SUPPORT_STATUS_UNSUPPORTED
)
print(self.package_info_field(dnf_output, "Support Periods", pkg_supported_period))
print(self.package_info_field(dnf_output, "", pkg_unsupported_period))
else:
pkg_unsupported_period = self.package_info_field(
dnf_output, f"from {support_statement['start_date']}", SUPPORT_STATUS_UNSUPPORTED
)
print(self.package_info_field(dnf_output, "Unsupported From", pkg_unsupported_period))
def print_package_info(
self, dnf_output, pkg, eol_id, nevra, state, support_statement, note_data
):
"""Print information about the given package"""
# print package name, nevra, OS state
print(self.package_info_field(dnf_output, "Name", pkg))
print(self.package_info_field(dnf_output, "Version", nevra))
print(self.package_info_field(dnf_output, "State", state))
# print support status and support periods
print(self.package_info_field(dnf_output, "Support Status", support_statement["status"]))
pkg_support_period = self.show_support_periods(dnf_output, support_statement)
# print support statements
print(
self.package_info_field(dnf_output, "Support Statement", support_statement["summary"])
)
print(self.package_info_field(dnf_output, "Link", support_statement["link"]))
print(self.package_info_field(dnf_output, "Other Info", support_statement["text"]))
if eol_id != "eol":
print(self.package_info_field(dnf_output, "Package Note", note_data[eol_id]))
print()
def get_pkg_os_state(self, package, package_states):
"""Return NEVRA and state of a package if it is installed or available to be installed"""
if package in package_states[STATE_INSTALLED]:
nevra = package_states[STATE_INSTALLED][package]["version"]
return STATE_INSTALLED, nevra
elif package in package_states[STATE_AVAILABLE]:
nevra = package_states[STATE_AVAILABLE][package]["version"]
return STATE_AVAILABLE, nevra
else:
return STATE_UNAVAILABLE, ""
def get_pkg_eol(self, pkg, package_data, support_statement_data, note_data, package_states):
"""Get package data from support_info.xml"""
with dnf.Base() as base:
base = dnf.Base()
dnf_output = dnf.cli.output.Output(base, base.conf)
for eol_id in package_data:
if pkg in package_data[eol_id]:
state, nevra = self.get_pkg_os_state(pkg, package_states)
support_statement = support_statement_data[eol_id]
# dnf supportinfo --pkg <pkg> --showxml
if self.opts.show_xml:
end_date = support_statement.get("end_date", "")
return self.show_xml(
pkg,
eol_id,
nevra,
support_statement["status"],
support_statement["summary"],
support_statement["start_date"],
end_date,
support_statement["text"],
support_statement["link"],
)
# dnf supportinfo --pkg <pkg>
self.print_package_info(
dnf_output, pkg, eol_id, nevra, state, support_statement, note_data
)
def _record_table(self, package, version, state, status, end_date, statement):
"""Sets formatting for pretty table"""
return (
f"{package:<42} {version:<36} {state:<18} {status:<18} {end_date:<18} {statement:<18}"
)
def print_support_statements_table(self, pkg, nevra, state, support_info):
"""
Print table based on supported, unsupported, installed
and uninstalled filters
"""
end_date = support_info.get("end_date", support_info["start_date"])
# generate table
pkg_statement = self._record_table(
pkg,
nevra,
state,
support_info["status"],
end_date,
support_info["summary"],
)
if state != STATE_UNAVAILABLE:
print(pkg_statement)
def display_support_statements(self, _filter, packages, statement, package_states):
"""Print support statements for supported, unsupported or all packages"""
for eol_id, data in packages.items():
for pkg in sorted(data):
state, nevra = self.get_pkg_os_state(pkg, package_states)
support_info = statement[eol_id]
# gather packages information per user input filter
if _filter == STATE_INSTALLED:
if state == _filter:
self.print_support_statements_table(pkg, nevra, state, support_info)
elif _filter == STATE_AVAILABLE:
if state == _filter:
self.print_support_statements_table(pkg, nevra, state, support_info)
elif _filter == SUPPORT_STATUS_SUPPORTED:
if support_info["status"] == _filter:
self.print_support_statements_table(pkg, nevra, state, support_info)
elif _filter == SUPPORT_STATUS_UNSUPPORTED:
if support_info["status"] == _filter:
self.print_support_statements_table(pkg, nevra, state, support_info)
elif _filter == "all":
self.print_support_statements_table(pkg, nevra, state, support_info)
else:
raise ValueError(f"unknown statement filter: {_filter}")
@staticmethod
def set_argparser(parser):
"""Parse package name as command line argument"""
parser.add_argument(
"--pkg", help="Display support statements for a package", dest="package"
)
parser.add_argument(
"--showxml",
help="Generate support info XML for a package",
action="store_true",
dest="show_xml",
)
parser.add_argument(
"--show",
help="Display support statements for packages",
dest="filter",
choices=["all", "supported", "unsupported", "installed", "available"],
)
def run(self):
"""Runs 'dnf supportinfo --pkg|--show <>' command"""
# support_info.xml will be stored at this same location of this file
plugin_dir = os.path.dirname(__file__)
support_info = os.path.join(plugin_dir, "support_info.xml")
# get data from <package> element
package_handler = PackageHandler()
xml.sax.parse(support_info, package_handler)
package_data = package_handler.pkg_data
# get data from <statement> element
statement_handler = StatementHandler()
xml.sax.parse(support_info, statement_handler)
support_statement_data = statement_handler.support_data
# get data from <note> element
note_handler = NoteHandler()
xml.sax.parse(support_info, note_handler)
note_data = note_handler.note_data
# get all installed and available packages info
package_states = self.get_packages_state()
# support statements for all/supported/unsupported/installed/available packages
if self.opts.filter:
self.display_support_statements(
self.opts.filter, package_data, support_statement_data, package_states
)
# show xml or get support info per package
if self.opts.package:
self.get_pkg_eol(
self.opts.package, package_data, support_statement_data, note_data, package_states
)