diff options
author | Jake Hunsaker <jhunsake@redhat.com> | 2020-12-17 16:45:54 -0500 |
---|---|---|
committer | Jake Hunsaker <jhunsake@redhat.com> | 2021-01-04 11:55:11 -0500 |
commit | 0c1098fa55d4fd4101193d8609b3845f90dddb35 (patch) | |
tree | 588e4e48ce2f952f65112e589c9f7185a070f24c | |
parent | 516f42719cdb98b7b0d035b115e7f9037616499d (diff) | |
download | sos-0c1098fa55d4fd4101193d8609b3845f90dddb35.tar.gz |
[PackageManager] Separate PackageManager from policies
Moves `PackageManager` out from `sos/policies/__init__.py` into a new
`sos/policies/package_managers` subdir.
Future commits will aim to canonicalize package manager subclasses for
policies to use, and ease the creation of new reusable package managers.
Related: #2349
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
-rw-r--r-- | sos/policies/__init__.py | 236 | ||||
-rw-r--r-- | sos/policies/distros/debian.py | 2 | ||||
-rw-r--r-- | sos/policies/distros/redhat.py | 3 | ||||
-rw-r--r-- | sos/policies/distros/suse.py | 2 | ||||
-rw-r--r-- | sos/policies/package_managers/__init__.py | 245 | ||||
-rw-r--r-- | tests/policy_tests.py | 3 |
6 files changed, 253 insertions, 238 deletions
diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py index 3dc7e1ad..a9172b2e 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -1,23 +1,18 @@ import os -import re import platform import time import json -import fnmatch import tempfile import random import string from pwd import getpwuid -from sos.utilities import (ImporterHelper, - import_module, - shell_out, - get_human_readable) +from sos.policies.package_managers import PackageManager +from sos.utilities import ImporterHelper, import_module, get_human_readable from sos.report.plugins import IndependentPlugin, ExperimentalPlugin from sos.options import SoSOptions from sos import _sos as _ from textwrap import fill -from pipes import quote PRESETS_PATH = "/etc/sos/presets.d" @@ -50,233 +45,6 @@ def load(cache={}, sysroot=None, init=None, probe_runtime=True, return cache['policy'] -class PackageManager(object): - """Encapsulates a package manager. If you provide a query_command to the - constructor it should print each package on the system in the following - format:: - - package name|package.version - - You may also subclass this class and provide a get_pkg_list method to - build the list of packages and versions. - - :cvar query_command: The command to use for querying packages - :vartype query_command: ``str`` or ``None`` - - :cvar verify_command: The command to use for verifying packages - :vartype verify_command: ``str`` or ``None`` - - :cvar verify_filter: Optional filter to use for controlling package - verification - :vartype verify_filter: ``str or ``None`` - - :cvar files_command: The command to use for getting file lists for packages - :vartype files_command: ``str`` or ``None`` - - :cvar chroot: Perform a chroot when executing `files_command` - :vartype chroot: ``bool`` - - :cvar remote_exec: If package manager is on a remote system (e.g. for - sos collect), prepend this SSH command to run remotely - :vartype remote_exec: ``str`` or ``None`` - """ - - query_command = None - verify_command = None - verify_filter = None - chroot = None - files = None - - def __init__(self, chroot=None, query_command=None, - verify_command=None, verify_filter=None, - files_command=None, remote_exec=None): - self.packages = {} - self.files = [] - - self.query_command = query_command if query_command else None - self.verify_command = verify_command if verify_command else None - self.verify_filter = verify_filter if verify_filter else None - self.files_command = files_command if files_command else None - - # if needed, append the remote command to these so that this returns - # the remote package details, not local - if remote_exec: - for cmd in ['query_command', 'verify_command', 'files_command']: - if getattr(self, cmd) is not None: - _cmd = getattr(self, cmd) - setattr(self, cmd, "%s %s" % (remote_exec, quote(_cmd))) - - if chroot: - self.chroot = chroot - - def all_pkgs_by_name(self, name): - """ - Get a list of packages that match name. - - :param name: The name of the package - :type name: ``str`` - - :returns: List of all packages matching `name` - :rtype: ``list`` - """ - return fnmatch.filter(self.all_pkgs().keys(), name) - - def all_pkgs_by_name_regex(self, regex_name, flags=0): - """ - Get a list of packages that match regex_name. - - :param regex_name: The regex to use for matching package names against - :type regex_name: ``str`` - - :param flags: Flags for the `re` module when matching `regex_name` - - :returns: All packages matching `regex_name` - :rtype: ``list`` - """ - reg = re.compile(regex_name, flags) - return [pkg for pkg in self.all_pkgs().keys() if reg.match(pkg)] - - def pkg_by_name(self, name): - """ - Get a single package that matches name. - - :param name: The name of the package - :type name: ``str`` - - :returns: The first package that matches `name` - :rtype: ``str`` - """ - pkgmatches = self.all_pkgs_by_name(name) - if (len(pkgmatches) != 0): - return self.all_pkgs_by_name(name)[-1] - else: - return None - - def get_pkg_list(self): - """Returns a dictionary of packages in the following - format:: - - {'package_name': {'name': 'package_name', - 'version': 'major.minor.version'}} - - """ - if self.query_command: - cmd = self.query_command - pkg_list = shell_out( - cmd, timeout=0, chroot=self.chroot - ).splitlines() - - for pkg in pkg_list: - if '|' not in pkg: - continue - elif pkg.count("|") == 1: - name, version = pkg.split("|") - release = None - elif pkg.count("|") == 2: - name, version, release = pkg.split("|") - self.packages[name] = { - 'name': name, - 'version': version.split(".") - } - release = release if release else None - self.packages[name]['release'] = release - - return self.packages - - def pkg_version(self, pkg): - """Returns the entry in self.packages for pkg if it exists - - :param pkg: The name of the package - :type pkg: ``str`` - - :returns: Package name and version, if package exists - :rtype: ``dict`` if found, else ``None`` - """ - pkgs = self.all_pkgs() - if pkg in pkgs: - return pkgs[pkg] - return None - - def all_pkgs(self): - """ - Get a list of all packages. - - :returns: All packages, with name and version, installed on the system - :rtype: ``dict`` - """ - if not self.packages: - self.packages = self.get_pkg_list() - return self.packages - - def pkg_nvra(self, pkg): - """Get the name, version, release, and architecture for a package - - :param pkg: The name of the package - :type pkg: ``str`` - - :returns: name, version, release, and arch of the package - :rtype: ``tuple`` - """ - fields = pkg.split("-") - version, release, arch = fields[-3:] - name = "-".join(fields[:-3]) - return (name, version, release, arch) - - def all_files(self): - """ - Get a list of files known by the package manager - - :returns: All files known by the package manager - :rtype: ``list`` - """ - if self.files_command and not self.files: - cmd = self.files_command - files = shell_out(cmd, timeout=0, chroot=self.chroot) - self.files = files.splitlines() - return self.files - - def build_verify_command(self, packages): - """build_verify_command(self, packages) -> str - Generate a command to verify the list of packages given - in ``packages`` using the native package manager's - verification tool. - - The command to be executed is returned as a string that - may be passed to a command execution routine (for e.g. - ``sos_get_command_output()``. - - :param packages: a string, or a list of strings giving - package names to be verified. - :returns: a string containing an executable command - that will perform verification of the given - packages. - :rtype: str or ``NoneType`` - """ - if not self.verify_command: - return None - - # The re.match(pkg) used by all_pkgs_by_name_regex() may return - # an empty list (`[[]]`) when no package matches: avoid building - # an rpm -V command line with the empty string as the package - # list in this case. - by_regex = self.all_pkgs_by_name_regex - verify_list = filter(None, map(by_regex, packages)) - - # No packages after regex match? - if not verify_list: - return None - - verify_packages = "" - for package_list in verify_list: - for package in package_list: - if any([f in package for f in self.verify_filter]): - continue - if len(verify_packages): - verify_packages += " " - verify_packages += package - return self.verify_command + " " + verify_packages - - #: Constants for on-disk preset fields DESC = "desc" NOTE = "note" diff --git a/sos/policies/distros/debian.py b/sos/policies/distros/debian.py index bef711b0..29c74dc9 100644 --- a/sos/policies/distros/debian.py +++ b/sos/policies/distros/debian.py @@ -8,7 +8,7 @@ from sos.report.plugins import DebianPlugin from sos.policies.distros import LinuxPolicy -from sos.policies import PackageManager +from sos.policies.package_managers import PackageManager import os diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py index 8c3ae621..589d0213 100644 --- a/sos/policies/distros/redhat.py +++ b/sos/policies/distros/redhat.py @@ -13,8 +13,9 @@ import sys import re from sos.report.plugins import RedHatPlugin -from sos.policies import PackageManager, PresetDefaults +from sos.policies import PresetDefaults from sos.policies.distros import LinuxPolicy +from sos.policies.package_managers import PackageManager from sos import _sos as _ from sos.options import SoSOptions diff --git a/sos/policies/distros/suse.py b/sos/policies/distros/suse.py index a4d73f56..928f4abb 100644 --- a/sos/policies/distros/suse.py +++ b/sos/policies/distros/suse.py @@ -12,7 +12,7 @@ import sys from sos.report.plugins import RedHatPlugin, SuSEPlugin from sos.policies.distros import LinuxPolicy -from sos.policies import PackageManager +from sos.policies.package_managers import PackageManager from sos import _sos as _ diff --git a/sos/policies/package_managers/__init__.py b/sos/policies/package_managers/__init__.py new file mode 100644 index 00000000..f9ce2a09 --- /dev/null +++ b/sos/policies/package_managers/__init__.py @@ -0,0 +1,245 @@ +# Copyright 2020 Red Hat, Inc. Jake Hunsaker <jhunsake@redhat.com> + +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + +import re +import fnmatch + +from sos.utilities import shell_out +from pipes import quote + + +class PackageManager(): + """Encapsulates a package manager. If you provide a query_command to the + constructor it should print each package on the system in the following + format:: + + package name|package.version + + You may also subclass this class and provide a get_pkg_list method to + build the list of packages and versions. + + :cvar query_command: The command to use for querying packages + :vartype query_command: ``str`` or ``None`` + + :cvar verify_command: The command to use for verifying packages + :vartype verify_command: ``str`` or ``None`` + + :cvar verify_filter: Optional filter to use for controlling package + verification + :vartype verify_filter: ``str or ``None`` + + :cvar files_command: The command to use for getting file lists for packages + :vartype files_command: ``str`` or ``None`` + + :cvar chroot: Perform a chroot when executing `files_command` + :vartype chroot: ``bool`` + + :cvar remote_exec: If package manager is on a remote system (e.g. for + sos collect), prepend this SSH command to run remotely + :vartype remote_exec: ``str`` or ``None`` + """ + + query_command = None + verify_command = None + verify_filter = None + chroot = None + files = None + + def __init__(self, chroot=None, query_command=None, + verify_command=None, verify_filter=None, + files_command=None, remote_exec=None): + self.packages = {} + self.files = [] + + self.query_command = query_command if query_command else None + self.verify_command = verify_command if verify_command else None + self.verify_filter = verify_filter if verify_filter else None + self.files_command = files_command if files_command else None + + # if needed, append the remote command to these so that this returns + # the remote package details, not local + if remote_exec: + for cmd in ['query_command', 'verify_command', 'files_command']: + if getattr(self, cmd) is not None: + _cmd = getattr(self, cmd) + setattr(self, cmd, "%s %s" % (remote_exec, quote(_cmd))) + + if chroot: + self.chroot = chroot + + def all_pkgs_by_name(self, name): + """ + Get a list of packages that match name. + + :param name: The name of the package + :type name: ``str`` + + :returns: List of all packages matching `name` + :rtype: ``list`` + """ + return fnmatch.filter(self.all_pkgs().keys(), name) + + def all_pkgs_by_name_regex(self, regex_name, flags=0): + """ + Get a list of packages that match regex_name. + + :param regex_name: The regex to use for matching package names against + :type regex_name: ``str`` + + :param flags: Flags for the `re` module when matching `regex_name` + + :returns: All packages matching `regex_name` + :rtype: ``list`` + """ + reg = re.compile(regex_name, flags) + return [pkg for pkg in self.all_pkgs().keys() if reg.match(pkg)] + + def pkg_by_name(self, name): + """ + Get a single package that matches name. + + :param name: The name of the package + :type name: ``str`` + + :returns: The first package that matches `name` + :rtype: ``str`` + """ + pkgmatches = self.all_pkgs_by_name(name) + if (len(pkgmatches) != 0): + return self.all_pkgs_by_name(name)[-1] + else: + return None + + def get_pkg_list(self): + """Returns a dictionary of packages in the following + format:: + + {'package_name': {'name': 'package_name', + 'version': 'major.minor.version'}} + + """ + if self.query_command: + cmd = self.query_command + pkg_list = shell_out( + cmd, timeout=0, chroot=self.chroot + ).splitlines() + + for pkg in pkg_list: + if '|' not in pkg: + continue + elif pkg.count("|") == 1: + name, version = pkg.split("|") + release = None + elif pkg.count("|") == 2: + name, version, release = pkg.split("|") + self.packages[name] = { + 'name': name, + 'version': version.split(".") + } + release = release if release else None + self.packages[name]['release'] = release + + return self.packages + + def pkg_version(self, pkg): + """Returns the entry in self.packages for pkg if it exists + + :param pkg: The name of the package + :type pkg: ``str`` + + :returns: Package name and version, if package exists + :rtype: ``dict`` if found, else ``None`` + """ + pkgs = self.all_pkgs() + if pkg in pkgs: + return pkgs[pkg] + return None + + def all_pkgs(self): + """ + Get a list of all packages. + + :returns: All packages, with name and version, installed on the system + :rtype: ``dict`` + """ + if not self.packages: + self.packages = self.get_pkg_list() + return self.packages + + def pkg_nvra(self, pkg): + """Get the name, version, release, and architecture for a package + + :param pkg: The name of the package + :type pkg: ``str`` + + :returns: name, version, release, and arch of the package + :rtype: ``tuple`` + """ + fields = pkg.split("-") + version, release, arch = fields[-3:] + name = "-".join(fields[:-3]) + return (name, version, release, arch) + + def all_files(self): + """ + Get a list of files known by the package manager + + :returns: All files known by the package manager + :rtype: ``list`` + """ + if self.files_command and not self.files: + cmd = self.files_command + files = shell_out(cmd, timeout=0, chroot=self.chroot) + self.files = files.splitlines() + return self.files + + def build_verify_command(self, packages): + """build_verify_command(self, packages) -> str + Generate a command to verify the list of packages given + in ``packages`` using the native package manager's + verification tool. + + The command to be executed is returned as a string that + may be passed to a command execution routine (for e.g. + ``sos_get_command_output()``. + + :param packages: a string, or a list of strings giving + package names to be verified. + :returns: a string containing an executable command + that will perform verification of the given + packages. + :rtype: str or ``NoneType`` + """ + if not self.verify_command: + return None + + # The re.match(pkg) used by all_pkgs_by_name_regex() may return + # an empty list (`[[]]`) when no package matches: avoid building + # an rpm -V command line with the empty string as the package + # list in this case. + by_regex = self.all_pkgs_by_name_regex + verify_list = filter(None, map(by_regex, packages)) + + # No packages after regex match? + if not verify_list: + return None + + verify_packages = "" + for package_list in verify_list: + for package in package_list: + if any([f in package for f in self.verify_filter]): + continue + if len(verify_packages): + verify_packages += " " + verify_packages += package + return self.verify_command + " " + verify_packages + + +# vim: set et ts=4 sw=4 : diff --git a/tests/policy_tests.py b/tests/policy_tests.py index 263ea352..4b248b70 100644 --- a/tests/policy_tests.py +++ b/tests/policy_tests.py @@ -7,7 +7,8 @@ # See the LICENSE file in the source distribution for further information. import unittest -from sos.policies import Policy, PackageManager, import_policy +from sos.policies import Policy, import_policy +from sos.policies.package_managers import PackageManager from sos.report.plugins import (Plugin, IndependentPlugin, RedHatPlugin, DebianPlugin) |