diff --git a/SOURCES/sos-bz1873185-estimate-only-option.patch b/SOURCES/sos-bz1873185-estimate-only-option.patch index c780809..a1a96c4 100644 --- a/SOURCES/sos-bz1873185-estimate-only-option.patch +++ b/SOURCES/sos-bz1873185-estimate-only-option.patch @@ -417,3 +417,900 @@ index ef61fb344..e0617b45e 100644 self.ui_log.info("") # package up and compress the results +From f22efe044f1f0565b57d6aeca2081a5227e0312c Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 14 Feb 2022 09:37:30 -0500 +Subject: [PATCH] [utilities] Don't try to chroot to / + +With the recent fix for sysroot being `None` to always being (correctly) +`/`, we should guard against situations where `sos_get_command_output()` +would now try to chroot to `/` before running any command. Incidentally, +this would also cause our unittests to fail if they were run by a +non-root user. + +Signed-off-by: Jake Hunsaker +--- + sos/utilities.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sos/utilities.py b/sos/utilities.py +index 6b13415b..d782123a 100644 +--- a/sos/utilities.py ++++ b/sos/utilities.py +@@ -120,7 +120,7 @@ def sos_get_command_output(command, timeout=TIMEOUT_DEFAULT, stderr=False, + # closure are caught in the parent (chroot and chdir are bound from + # the enclosing scope). + def _child_prep_fn(): +- if (chroot): ++ if chroot and chroot != '/': + os.chroot(chroot) + if (chdir): + os.chdir(chdir) +-- +2.34.1 +From 3d064102f8ca6662fd9602512e1cb05cf8746dfd Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 27 Sep 2021 19:01:16 -0400 +Subject: [PATCH] [Systemd, Policy] Correct InitSystem chrooting when chroot is + needed + +This commit resolves a situation in which `sos` is being run in a +container but the `SystemdInit` InitSystem would not properly load +information from the host, thus causing the `Plugin.is_service*()` +methods to erroneously fail or return `False`. + +Fix this scenario by pulling the `_container_init()` and related logic +to check for a containerized host sysroot out of the Red Hat specific +policy and into the base `LinuxPolicy` class so that the init system can +be initialized with the correct sysroot, which is now used to chroot the +calls to the relevant `systemctl` commands. + +For now, this does impose the use of looking for the `container` env var +(automatically set by docker, podman, and crio regardless of +distribution) and the use of the `HOST` env var to read where the host's +`/` filesystem is mounted within the container. If desired in the +future, this can be changed to allow policy-specific overrides. For now +however, this extends host collection via an sos container for all +distributions currently shipping sos. + +Note that this issue only affected the `InitSystem` abstraction for +loading information about local services, and did not affect init system +related commands called by plugins as part of those collections. + +Signed-off-by: Jake Hunsaker +--- + sos/policies/distros/__init__.py | 28 ++++++++++++++++++++++++++- + sos/policies/distros/redhat.py | 27 +------------------------- + sos/policies/init_systems/__init__.py | 13 +++++++++++-- + sos/policies/init_systems/systemd.py | 7 ++++--- + 4 files changed, 43 insertions(+), 32 deletions(-) + +diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py +index f5b9fd5b01..c33a356a75 100644 +--- a/sos/policies/distros/__init__.py ++++ b/sos/policies/distros/__init__.py +@@ -29,6 +29,10 @@ + except ImportError: + REQUESTS_LOADED = False + ++# Container environment variables for detecting if we're in a container ++ENV_CONTAINER = 'container' ++ENV_HOST_SYSROOT = 'HOST' ++ + + class LinuxPolicy(Policy): + """This policy is meant to be an abc class that provides common +@@ -69,10 +73,17 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True): + probe_runtime=probe_runtime) + self.init_kernel_modules() + ++ # need to set _host_sysroot before PackageManager() ++ if sysroot: ++ self._container_init() ++ self._host_sysroot = sysroot ++ else: ++ sysroot = self._container_init() ++ + if init is not None: + self.init_system = init + elif os.path.isdir("/run/systemd/system/"): +- self.init_system = SystemdInit() ++ self.init_system = SystemdInit(chroot=sysroot) + else: + self.init_system = InitSystem() + +@@ -130,6 +141,21 @@ def get_local_name(self): + def sanitize_filename(self, name): + return re.sub(r"[^-a-z,A-Z.0-9]", "", name) + ++ def _container_init(self): ++ """Check if sos is running in a container and perform container ++ specific initialisation based on ENV_HOST_SYSROOT. ++ """ ++ if ENV_CONTAINER in os.environ: ++ if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: ++ self._in_container = True ++ if ENV_HOST_SYSROOT in os.environ: ++ self._host_sysroot = os.environ[ENV_HOST_SYSROOT] ++ use_sysroot = self._in_container and self._host_sysroot is not None ++ if use_sysroot: ++ host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) ++ self._tmp_dir = host_tmp_dir ++ return self._host_sysroot if use_sysroot else None ++ + def init_kernel_modules(self): + """Obtain a list of loaded kernel modules to reference later for plugin + enablement and SoSPredicate checks +diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py +index b3a84336be..3476e21fb2 100644 +--- a/sos/policies/distros/redhat.py ++++ b/sos/policies/distros/redhat.py +@@ -17,7 +17,7 @@ + from sos.presets.redhat import (RHEL_PRESETS, ATOMIC_PRESETS, RHV, RHEL, + CB, RHOSP, RHOCP, RH_CFME, RH_SATELLITE, + ATOMIC) +-from sos.policies.distros import LinuxPolicy ++from sos.policies.distros import LinuxPolicy, ENV_HOST_SYSROOT + from sos.policies.package_managers.rpm import RpmPackageManager + from sos import _sos as _ + +@@ -56,12 +56,6 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, + super(RedHatPolicy, self).__init__(sysroot=sysroot, init=init, + probe_runtime=probe_runtime) + self.usrmove = False +- # need to set _host_sysroot before PackageManager() +- if sysroot: +- self._container_init() +- self._host_sysroot = sysroot +- else: +- sysroot = self._container_init() + + self.package_manager = RpmPackageManager(chroot=sysroot, + remote_exec=remote_exec) +@@ -140,21 +134,6 @@ def transform_path(path): + else: + return files + +- def _container_init(self): +- """Check if sos is running in a container and perform container +- specific initialisation based on ENV_HOST_SYSROOT. +- """ +- if ENV_CONTAINER in os.environ: +- if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: +- self._in_container = True +- if ENV_HOST_SYSROOT in os.environ: +- self._host_sysroot = os.environ[ENV_HOST_SYSROOT] +- use_sysroot = self._in_container and self._host_sysroot is not None +- if use_sysroot: +- host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) +- self._tmp_dir = host_tmp_dir +- return self._host_sysroot if use_sysroot else None +- + def runlevel_by_service(self, name): + from subprocess import Popen, PIPE + ret = [] +@@ -183,10 +162,6 @@ def get_tmp_dir(self, opt_tmp_dir): + return opt_tmp_dir + + +-# Container environment variables on Red Hat systems. +-ENV_CONTAINER = 'container' +-ENV_HOST_SYSROOT = 'HOST' +- + # Legal disclaimer text for Red Hat products + disclaimer_text = """ + Any information provided to %(vendor)s will be treated in \ +diff --git a/sos/policies/init_systems/__init__.py b/sos/policies/init_systems/__init__.py +index dd663e6522..beac44cee3 100644 +--- a/sos/policies/init_systems/__init__.py ++++ b/sos/policies/init_systems/__init__.py +@@ -29,9 +29,14 @@ class InitSystem(): + status of services + :type query_cmd: ``str`` + ++ :param chroot: Location to chroot to for any command execution, i.e. the ++ sysroot if we're running in a container ++ :type chroot: ``str`` or ``None`` ++ + """ + +- def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None): ++ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None, ++ chroot=None): + """Initialize a new InitSystem()""" + + self.services = {} +@@ -39,6 +44,7 @@ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None): + self.init_cmd = init_cmd + self.list_cmd = "%s %s" % (self.init_cmd, list_cmd) or None + self.query_cmd = "%s %s" % (self.init_cmd, query_cmd) or None ++ self.chroot = chroot + + def is_enabled(self, name): + """Check if given service name is enabled +@@ -108,7 +114,10 @@ def _query_service(self, name): + """Query an individual service""" + if self.query_cmd: + try: +- return sos_get_command_output("%s %s" % (self.query_cmd, name)) ++ return sos_get_command_output( ++ "%s %s" % (self.query_cmd, name), ++ chroot=self.chroot ++ ) + except Exception: + return None + return None +diff --git a/sos/policies/init_systems/systemd.py b/sos/policies/init_systems/systemd.py +index 1b138f97b3..76dc57e27f 100644 +--- a/sos/policies/init_systems/systemd.py ++++ b/sos/policies/init_systems/systemd.py +@@ -15,11 +15,12 @@ + class SystemdInit(InitSystem): + """InitSystem abstraction for SystemD systems""" + +- def __init__(self): ++ def __init__(self, chroot=None): + super(SystemdInit, self).__init__( + init_cmd='systemctl', + list_cmd='list-unit-files --type=service', +- query_cmd='status' ++ query_cmd='status', ++ chroot=chroot + ) + self.load_all_services() + +@@ -30,7 +31,7 @@ def parse_query(self, output): + return 'unknown' + + def load_all_services(self): +- svcs = shell_out(self.list_cmd).splitlines()[1:] ++ svcs = shell_out(self.list_cmd, chroot=self.chroot).splitlines()[1:] + for line in svcs: + try: + name = line.split('.service')[0] +From e869bc84c714bfc2249bbcb84e14908049ee42c4 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 27 Sep 2021 12:07:08 -0400 +Subject: [PATCH] [Plugin,utilities] Add sysroot wrapper for os.path.join + +Adds a wrapper for `os.path.join()` which accounts for non-/ sysroots, +like we have done previously for other `os.path` methods. Further +updates `Plugin()` to use this wrapper where appropriate. + +Signed-off-by: Jake Hunsaker +--- + sos/report/plugins/__init__.py | 43 +++++++++++++++++----------------- + sos/utilities.py | 6 +++++ + 2 files changed, 28 insertions(+), 21 deletions(-) + +diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py +index c635b8de9..1f84bca49 100644 +--- a/sos/report/plugins/__init__.py ++++ b/sos/report/plugins/__init__.py +@@ -13,7 +13,7 @@ + from sos.utilities import (sos_get_command_output, import_module, grep, + fileobj, tail, is_executable, TIMEOUT_DEFAULT, + path_exists, path_isdir, path_isfile, path_islink, +- listdir) ++ listdir, path_join) + + import os + import glob +@@ -708,19 +708,6 @@ def _log_info(self, msg): + def _log_debug(self, msg): + self.soslog.debug(self._format_msg(msg)) + +- def join_sysroot(self, path): +- """Join a given path with the configured sysroot +- +- :param path: The filesystem path that needs to be joined +- :type path: ``str`` +- +- :returns: The joined filesystem path +- :rtype: ``str`` +- """ +- if path[0] == os.sep: +- path = path[1:] +- return os.path.join(self.sysroot, path) +- + def strip_sysroot(self, path): + """Remove the configured sysroot from a filesystem path + +@@ -1176,7 +1163,7 @@ def _copy_dir(self, srcpath): + + def _get_dest_for_srcpath(self, srcpath): + if self.use_sysroot(): +- srcpath = self.join_sysroot(srcpath) ++ srcpath = self.path_join(srcpath) + for copied in self.copied_files: + if srcpath == copied["srcpath"]: + return copied["dstpath"] +@@ -1284,7 +1271,7 @@ def add_forbidden_path(self, forbidden, recursive=False): + forbidden = [forbidden] + + if self.use_sysroot(): +- forbidden = [self.join_sysroot(f) for f in forbidden] ++ forbidden = [self.path_join(f) for f in forbidden] + + for forbid in forbidden: + self._log_info("adding forbidden path '%s'" % forbid) +@@ -1438,7 +1425,7 @@ def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None, + since = self.get_option('since') + + logarchive_pattern = re.compile(r'.*((\.(zip|gz|bz2|xz))|[-.][\d]+)$') +- configfile_pattern = re.compile(r"^%s/*" % self.join_sysroot("etc")) ++ configfile_pattern = re.compile(r"^%s/*" % self.path_join("etc")) + + if not self.test_predicate(pred=pred): + self._log_info("skipped copy spec '%s' due to predicate (%s)" % +@@ -1468,7 +1455,7 @@ def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None, + return False + + if self.use_sysroot(): +- copyspec = self.join_sysroot(copyspec) ++ copyspec = self.path_join(copyspec) + + files = self._expand_copy_spec(copyspec) + +@@ -1683,7 +1670,7 @@ def _add_device_cmd(self, cmds, devices, timeout=None, sizelimit=None, + if not _dev_ok: + continue + if prepend_path: +- device = os.path.join(prepend_path, device) ++ device = self.path_join(prepend_path, device) + _cmd = cmd % {'dev': device} + self._add_cmd_output(cmd=_cmd, timeout=timeout, + sizelimit=sizelimit, chroot=chroot, +@@ -2592,7 +2579,7 @@ def __expand(paths): + if self.path_isfile(path) or self.path_islink(path): + found_paths.append(path) + elif self.path_isdir(path) and self.listdir(path): +- found_paths.extend(__expand(os.path.join(path, '*'))) ++ found_paths.extend(__expand(self.path_join(path, '*'))) + else: + found_paths.append(path) + except PermissionError: +@@ -2608,7 +2595,7 @@ def __expand(paths): + if (os.access(copyspec, os.R_OK) and self.path_isdir(copyspec) and + self.listdir(copyspec)): + # the directory exists and is non-empty, recurse through it +- copyspec = os.path.join(copyspec, '*') ++ copyspec = self.path_join(copyspec, '*') + expanded = glob.glob(copyspec, recursive=True) + recursed_files = [] + for _path in expanded: +@@ -2877,6 +2864,20 @@ def listdir(self, path): + """ + return listdir(path, self.commons['cmdlineopts'].sysroot) + ++ def path_join(self, path, *p): ++ """Helper to call the sos.utilities wrapper that allows the ++ corresponding `os` call to account for sysroot ++ ++ :param path: The leading path passed to os.path.join() ++ :type path: ``str`` ++ ++ :param p: Following path section(s) to be joined with ``path``, ++ an empty parameter will result in a path that ends with ++ a separator ++ :type p: ``str`` ++ """ ++ return path_join(path, *p, sysroot=self.sysroot) ++ + def postproc(self): + """Perform any postprocessing. To be replaced by a plugin if required. + """ +diff --git a/sos/utilities.py b/sos/utilities.py +index c940e066d..b75751539 100644 +--- a/sos/utilities.py ++++ b/sos/utilities.py +@@ -242,6 +242,12 @@ def listdir(path, sysroot): + return _os_wrapper(path, sysroot, 'listdir', os) + + ++def path_join(path, *p, sysroot=os.sep): ++ if not path.startswith(sysroot): ++ path = os.path.join(sysroot, path.lstrip(os.sep)) ++ return os.path.join(path, *p) ++ ++ + class AsyncReader(threading.Thread): + """Used to limit command output to a given size without deadlocking + sos. +From 9596473d1779b9c48e9923c220aaf2b8d9b3bebf Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Thu, 18 Nov 2021 13:17:14 -0500 +Subject: [PATCH] [global] Align sysroot determination and usage across sos + +The determination of sysroot - being automatic, user-specified, or +controlled via environment variables in a container - has gotten muddied +over time. This has resulted in different parts of the project; +`Policy`, `Plugin`, `SoSComponent`, etc... to not always be in sync when +sysroot is not `/`, thus causing varying and unexpected/unintended +behavior. + +Fix this by only determining sysroot within `Policy()` initialization, +and then using that determination across all aspects of the project that +use or reference sysroot. + +This results in several changes: + +- `PackageManager()` will now (again) correctly reference host package + lists when sos is run in a container. + +- `ContainerRuntime()` is now able to activate when sos is running in a + container. + +- Plugins will now properly use sysroot for _all_ plugin enablement + triggers. + +- Plugins, Policy, and SoSComponents now all reference the + `self.sysroot` variable, rather than changing between `sysroot`. +`_host_sysroot`, and `commons['sysroot']`. `_host_sysroot` has been +removed from `Policy`. + +Signed-off-by: Jake Hunsaker +--- + sos/archive.py | 2 +- + sos/component.py | 2 +- + sos/policies/__init__.py | 11 +---------- + sos/policies/distros/__init__.py | 33 +++++++++++++++++++------------ + sos/policies/distros/debian.py | 2 +- + sos/policies/distros/redhat.py | 3 +-- + sos/policies/runtimes/__init__.py | 15 +++++++++----- + sos/policies/runtimes/docker.py | 4 ++-- + sos/report/__init__.py | 6 ++---- + sos/report/plugins/__init__.py | 22 +++++++++++---------- + sos/report/plugins/unpackaged.py | 7 ++++--- + sos/utilities.py | 13 ++++++++---- + 12 files changed, 64 insertions(+), 56 deletions(-) + +diff --git a/sos/archive.py b/sos/archive.py +index b02b247595..e3c68b7789 100644 +--- a/sos/archive.py ++++ b/sos/archive.py +@@ -153,7 +153,7 @@ def dest_path(self, name): + return (os.path.join(self._archive_root, name)) + + def join_sysroot(self, path): +- if path.startswith(self.sysroot): ++ if not self.sysroot or path.startswith(self.sysroot): + return path + if path[0] == os.sep: + path = path[1:] +diff --git a/sos/component.py b/sos/component.py +index 5ac6e47f4f..dba0aabf2b 100644 +--- a/sos/component.py ++++ b/sos/component.py +@@ -109,7 +109,7 @@ def __init__(self, parser, parsed_args, cmdline_args): + try: + import sos.policies + self.policy = sos.policies.load(sysroot=self.opts.sysroot) +- self.sysroot = self.policy.host_sysroot() ++ self.sysroot = self.policy.sysroot + except KeyboardInterrupt: + self._exit(0) + self._is_root = self.policy.is_root() +diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py +index fb8db1d724..ef9188deb4 100644 +--- a/sos/policies/__init__.py ++++ b/sos/policies/__init__.py +@@ -110,7 +110,6 @@ class Policy(object): + presets = {"": PresetDefaults()} + presets_path = PRESETS_PATH + _in_container = False +- _host_sysroot = '/' + + def __init__(self, sysroot=None, probe_runtime=True): + """Subclasses that choose to override this initializer should call +@@ -124,7 +123,7 @@ def __init__(self, sysroot=None, probe_runtime=True): + self.package_manager = PackageManager() + self.valid_subclasses = [IndependentPlugin] + self.set_exec_path() +- self._host_sysroot = sysroot ++ self.sysroot = sysroot + self.register_presets(GENERIC_PRESETS) + + def check(self, remote=''): +@@ -177,14 +176,6 @@ def in_container(self): + """ + return self._in_container + +- def host_sysroot(self): +- """Get the host's default sysroot +- +- :returns: Host sysroot +- :rtype: ``str`` or ``None`` +- """ +- return self._host_sysroot +- + def dist_version(self): + """ + Return the OS version +diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py +index 7bdc81b852..c69fc1e73c 100644 +--- a/sos/policies/distros/__init__.py ++++ b/sos/policies/distros/__init__.py +@@ -71,19 +71,18 @@ class LinuxPolicy(Policy): + def __init__(self, sysroot=None, init=None, probe_runtime=True): + super(LinuxPolicy, self).__init__(sysroot=sysroot, + probe_runtime=probe_runtime) +- self.init_kernel_modules() + +- # need to set _host_sysroot before PackageManager() + if sysroot: +- self._container_init() +- self._host_sysroot = sysroot ++ self.sysroot = sysroot + else: +- sysroot = self._container_init() ++ self.sysroot = self._container_init() ++ ++ self.init_kernel_modules() + + if init is not None: + self.init_system = init + elif os.path.isdir("/run/systemd/system/"): +- self.init_system = SystemdInit(chroot=sysroot) ++ self.init_system = SystemdInit(chroot=self.sysroot) + else: + self.init_system = InitSystem() + +@@ -149,27 +148,30 @@ def _container_init(self): + if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: + self._in_container = True + if ENV_HOST_SYSROOT in os.environ: +- self._host_sysroot = os.environ[ENV_HOST_SYSROOT] +- use_sysroot = self._in_container and self._host_sysroot is not None ++ _host_sysroot = os.environ[ENV_HOST_SYSROOT] ++ use_sysroot = self._in_container and _host_sysroot is not None + if use_sysroot: +- host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) ++ host_tmp_dir = os.path.abspath(_host_sysroot + self._tmp_dir) + self._tmp_dir = host_tmp_dir +- return self._host_sysroot if use_sysroot else None ++ return _host_sysroot if use_sysroot else None + + def init_kernel_modules(self): + """Obtain a list of loaded kernel modules to reference later for plugin + enablement and SoSPredicate checks + """ + self.kernel_mods = [] ++ release = os.uname().release + + # first load modules from lsmod +- lines = shell_out("lsmod", timeout=0).splitlines() ++ lines = shell_out("lsmod", timeout=0, chroot=self.sysroot).splitlines() + self.kernel_mods.extend([ + line.split()[0].strip() for line in lines[1:] + ]) + + # next, include kernel builtins +- builtins = "/usr/lib/modules/%s/modules.builtin" % os.uname().release ++ builtins = self.join_sysroot( ++ "/usr/lib/modules/%s/modules.builtin" % release ++ ) + try: + with open(builtins, "r") as mfile: + for line in mfile: +@@ -186,7 +188,7 @@ def init_kernel_modules(self): + 'dm_mod': 'CONFIG_BLK_DEV_DM' + } + +- booted_config = "/boot/config-%s" % os.uname().release ++ booted_config = self.join_sysroot("/boot/config-%s" % release) + kconfigs = [] + try: + with open(booted_config, "r") as kfile: +@@ -200,6 +202,11 @@ def init_kernel_modules(self): + if config_strings[builtin] in kconfigs: + self.kernel_mods.append(builtin) + ++ def join_sysroot(self, path): ++ if self.sysroot and self.sysroot != '/': ++ path = os.path.join(self.sysroot, path.lstrip('/')) ++ return path ++ + def pre_work(self): + # this method will be called before the gathering begins + +diff --git a/sos/policies/distros/debian.py b/sos/policies/distros/debian.py +index 95b389a65e..639fd5eba3 100644 +--- a/sos/policies/distros/debian.py ++++ b/sos/policies/distros/debian.py +@@ -27,7 +27,7 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, + remote_exec=None): + super(DebianPolicy, self).__init__(sysroot=sysroot, init=init, + probe_runtime=probe_runtime) +- self.package_manager = DpkgPackageManager(chroot=sysroot, ++ self.package_manager = DpkgPackageManager(chroot=self.sysroot, + remote_exec=remote_exec) + self.valid_subclasses += [DebianPlugin] + +diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py +index eb44240736..4b14abaf3a 100644 +--- a/sos/policies/distros/redhat.py ++++ b/sos/policies/distros/redhat.py +@@ -42,7 +42,6 @@ class RedHatPolicy(LinuxPolicy): + _redhat_release = '/etc/redhat-release' + _tmp_dir = "/var/tmp" + _in_container = False +- _host_sysroot = '/' + default_scl_prefix = '/opt/rh' + name_pattern = 'friendly' + upload_url = None +@@ -57,7 +56,7 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, + probe_runtime=probe_runtime) + self.usrmove = False + +- self.package_manager = RpmPackageManager(chroot=sysroot, ++ self.package_manager = RpmPackageManager(chroot=self.sysroot, + remote_exec=remote_exec) + + self.valid_subclasses += [RedHatPlugin] +diff --git a/sos/policies/runtimes/__init__.py b/sos/policies/runtimes/__init__.py +index f28d6a1df3..2e60ad2361 100644 +--- a/sos/policies/runtimes/__init__.py ++++ b/sos/policies/runtimes/__init__.py +@@ -64,7 +64,7 @@ def check_is_active(self): + :returns: ``True`` if the runtime is active, else ``False`` + :rtype: ``bool`` + """ +- if is_executable(self.binary): ++ if is_executable(self.binary, self.policy.sysroot): + self.active = True + return True + return False +@@ -78,7 +78,7 @@ def get_containers(self, get_all=False): + containers = [] + _cmd = "%s ps %s" % (self.binary, '-a' if get_all else '') + if self.active: +- out = sos_get_command_output(_cmd) ++ out = sos_get_command_output(_cmd, chroot=self.policy.sysroot) + if out['status'] == 0: + for ent in out['output'].splitlines()[1:]: + ent = ent.split() +@@ -112,8 +112,10 @@ def get_images(self): + images = [] + fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}' + if self.active: +- out = sos_get_command_output("%s images --format '%s'" +- % (self.binary, fmt)) ++ out = sos_get_command_output( ++ "%s images --format '%s'" % (self.binary, fmt), ++ chroot=self.policy.sysroot ++ ) + if out['status'] == 0: + for ent in out['output'].splitlines(): + ent = ent.split() +@@ -129,7 +131,10 @@ def get_volumes(self): + """ + vols = [] + if self.active: +- out = sos_get_command_output("%s volume ls" % self.binary) ++ out = sos_get_command_output( ++ "%s volume ls" % self.binary, ++ chroot=self.policy.sysroot ++ ) + if out['status'] == 0: + for ent in out['output'].splitlines()[1:]: + ent = ent.split() +diff --git a/sos/policies/runtimes/docker.py b/sos/policies/runtimes/docker.py +index 759dfaf6a0..e81f580ec3 100644 +--- a/sos/policies/runtimes/docker.py ++++ b/sos/policies/runtimes/docker.py +@@ -18,9 +18,9 @@ class DockerContainerRuntime(ContainerRuntime): + name = 'docker' + binary = 'docker' + +- def check_is_active(self): ++ def check_is_active(self, sysroot=None): + # the daemon must be running +- if (is_executable('docker') and ++ if (is_executable('docker', sysroot) and + (self.policy.init_system.is_running('docker') or + self.policy.init_system.is_running('snap.docker.dockerd'))): + self.active = True +diff --git a/sos/report/__init__.py b/sos/report/__init__.py +index a4c92accd3..a6c72778fc 100644 +--- a/sos/report/__init__.py ++++ b/sos/report/__init__.py +@@ -173,14 +173,12 @@ def __init__(self, parser, args, cmdline): + self._set_directories() + + msg = "default" +- host_sysroot = self.policy.host_sysroot() ++ self.sysroot = self.policy.sysroot + # set alternate system root directory + if self.opts.sysroot: + msg = "cmdline" +- self.sysroot = self.opts.sysroot +- elif self.policy.in_container() and host_sysroot != os.sep: ++ elif self.policy.in_container() and self.sysroot != os.sep: + msg = "policy" +- self.sysroot = host_sysroot + self.soslog.debug("set sysroot to '%s' (%s)" % (self.sysroot, msg)) + + if self.opts.chroot not in chroot_modes: +diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py +index 46028bb124..e180ae1727 100644 +--- a/sos/report/plugins/__init__.py ++++ b/sos/report/plugins/__init__.py +@@ -724,7 +724,7 @@ def strip_sysroot(self, path): + """ + if not self.use_sysroot(): + return path +- if path.startswith(self.sysroot): ++ if self.sysroot and path.startswith(self.sysroot): + return path[len(self.sysroot):] + return path + +@@ -743,8 +743,10 @@ def tmp_in_sysroot(self): + ``False`` + :rtype: ``bool`` + """ +- paths = [self.sysroot, self.archive.get_tmp_dir()] +- return os.path.commonprefix(paths) == self.sysroot ++ # if sysroot is still None, that implies '/' ++ _sysroot = self.sysroot or '/' ++ paths = [_sysroot, self.archive.get_tmp_dir()] ++ return os.path.commonprefix(paths) == _sysroot + + def is_installed(self, package_name): + """Is the package $package_name installed? +@@ -2621,7 +2623,7 @@ def __expand(paths): + return list(set(expanded)) + + def _collect_copy_specs(self): +- for path in self.copy_paths: ++ for path in sorted(self.copy_paths, reverse=True): + self._log_info("collecting path '%s'" % path) + self._do_copy_path(path) + self.generate_copyspec_tags() +@@ -2749,7 +2751,7 @@ def _check_plugin_triggers(self, files, packages, commands, services, + + return ((any(self.path_exists(fname) for fname in files) or + any(self.is_installed(pkg) for pkg in packages) or +- any(is_executable(cmd) for cmd in commands) or ++ any(is_executable(cmd, self.sysroot) for cmd in commands) or + any(self.is_module_loaded(mod) for mod in self.kernel_mods) or + any(self.is_service(svc) for svc in services) or + any(self.container_exists(cntr) for cntr in containers)) and +@@ -2817,7 +2819,7 @@ def path_exists(self, path): + :returns: True if the path exists in sysroot, else False + :rtype: ``bool`` + """ +- return path_exists(path, self.commons['cmdlineopts'].sysroot) ++ return path_exists(path, self.sysroot) + + def path_isdir(self, path): + """Helper to call the sos.utilities wrapper that allows the +@@ -2830,7 +2832,7 @@ def path_isdir(self, path): + :returns: True if the path is a dir, else False + :rtype: ``bool`` + """ +- return path_isdir(path, self.commons['cmdlineopts'].sysroot) ++ return path_isdir(path, self.sysroot) + + def path_isfile(self, path): + """Helper to call the sos.utilities wrapper that allows the +@@ -2843,7 +2845,7 @@ def path_isfile(self, path): + :returns: True if the path is a file, else False + :rtype: ``bool`` + """ +- return path_isfile(path, self.commons['cmdlineopts'].sysroot) ++ return path_isfile(path, self.sysroot) + + def path_islink(self, path): + """Helper to call the sos.utilities wrapper that allows the +@@ -2856,7 +2858,7 @@ def path_islink(self, path): + :returns: True if the path is a link, else False + :rtype: ``bool`` + """ +- return path_islink(path, self.commons['cmdlineopts'].sysroot) ++ return path_islink(path, self.sysroot) + + def listdir(self, path): + """Helper to call the sos.utilities wrapper that allows the +@@ -2869,7 +2871,7 @@ def listdir(self, path): + :returns: Contents of path, if it is a directory + :rtype: ``list`` + """ +- return listdir(path, self.commons['cmdlineopts'].sysroot) ++ return listdir(path, self.sysroot) + + def path_join(self, path, *p): + """Helper to call the sos.utilities wrapper that allows the +diff --git a/sos/report/plugins/unpackaged.py b/sos/report/plugins/unpackaged.py +index 772b1d1fbb..24203c4b13 100644 +--- a/sos/report/plugins/unpackaged.py ++++ b/sos/report/plugins/unpackaged.py +@@ -58,10 +58,11 @@ def format_output(files): + """ + expanded = [] + for f in files: +- if self.path_islink(f): +- expanded.append("{} -> {}".format(f, os.readlink(f))) ++ fp = self.path_join(f) ++ if self.path_islink(fp): ++ expanded.append("{} -> {}".format(fp, os.readlink(fp))) + else: +- expanded.append(f) ++ expanded.append(fp) + return expanded + + # Check command predicate to avoid costly processing +diff --git a/sos/utilities.py b/sos/utilities.py +index b757515397..d66309334b 100644 +--- a/sos/utilities.py ++++ b/sos/utilities.py +@@ -96,11 +96,15 @@ def grep(pattern, *files_or_paths): + return matches + + +-def is_executable(command): ++def is_executable(command, sysroot=None): + """Returns if a command matches an executable on the PATH""" + + paths = os.environ.get("PATH", "").split(os.path.pathsep) + candidates = [command] + [os.path.join(p, command) for p in paths] ++ if sysroot: ++ candidates += [ ++ os.path.join(sysroot, c.lstrip('/')) for c in candidates ++ ] + return any(os.access(path, os.X_OK) for path in candidates) + + +@@ -216,8 +220,9 @@ def get_human_readable(size, precision=2): + + + def _os_wrapper(path, sysroot, method, module=os.path): +- if sysroot not in [None, '/']: +- path = os.path.join(sysroot, path.lstrip('/')) ++ if sysroot and sysroot != os.sep: ++ if not path.startswith(sysroot): ++ path = os.path.join(sysroot, path.lstrip('/')) + _meth = getattr(module, method) + return _meth(path) + +@@ -243,7 +248,7 @@ def listdir(path, sysroot): + + + def path_join(path, *p, sysroot=os.sep): +- if not path.startswith(sysroot): ++ if sysroot and not path.startswith(sysroot): + path = os.path.join(sysroot, path.lstrip(os.sep)) + return os.path.join(path, *p) + +From a43124e1f6217107838eed4d70339d100cbbc77a Mon Sep 17 00:00:00 2001 +From: Pavel Moravec +Date: Wed, 9 Feb 2022 19:45:27 +0100 +Subject: [PATCH] [policies] Set fallback to None sysroot + +9596473 commit added a regression allowing to set sysroot to None +when running sos report on a regular system (outside a container). In +such a case, we need to fallback to '/' sysroot. + +Resolves: #2846 + +Signed-off-by: Pavel Moravec +--- + sos/policies/distros/__init__.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py +index f3c1de11..9048f1c4 100644 +--- a/sos/policies/distros/__init__.py ++++ b/sos/policies/distros/__init__.py +@@ -78,7 +78,7 @@ class LinuxPolicy(Policy): + if sysroot: + self.sysroot = sysroot + else: +- self.sysroot = self._container_init() ++ self.sysroot = self._container_init() or '/' + + self.init_kernel_modules() + +-- +2.34.1 + diff --git a/SOURCES/sos-bz2023867-cleaner-hostnames-improvements.patch b/SOURCES/sos-bz2023867-cleaner-hostnames-improvements.patch index d257fdf..b129f9e 100644 --- a/SOURCES/sos-bz2023867-cleaner-hostnames-improvements.patch +++ b/SOURCES/sos-bz2023867-cleaner-hostnames-improvements.patch @@ -1797,3 +1797,33 @@ index 229c7de4..3208a655 100644 -- 2.31.1 +From 7ebb2ce0bcd13c1b3aada648aceb20b5aff636d9 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 15 Feb 2022 14:18:02 -0500 +Subject: [PATCH] [host] Skip entire /etc/sos/cleaner directory + +While `default_mapping` is typically the only file expected under +`/etc/sos/cleaner/` it is possible for other mapping files (such as +backups) to appear there. + +Make the `add_forbidden_path()` spec here target the entire cleaner +directory to avoid ever capturing these map files. + +Signed-off-by: Jake Hunsaker +--- + sos/report/plugins/host.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sos/report/plugins/host.py b/sos/report/plugins/host.py +index 5e21da7b8e..95a3b9cd95 100644 +--- a/sos/report/plugins/host.py ++++ b/sos/report/plugins/host.py +@@ -20,7 +20,7 @@ class Host(Plugin, IndependentPlugin): + + def setup(self): + +- self.add_forbidden_path('/etc/sos/cleaner/default_mapping') ++ self.add_forbidden_path('/etc/sos/cleaner') + + self.add_cmd_output('hostname', root_symlink='hostname') + self.add_cmd_output('uptime', root_symlink='uptime') diff --git a/SOURCES/sos-bz2036697-ocp-backports.patch b/SOURCES/sos-bz2036697-ocp-backports.patch index 5a44250..3e53e93 100644 --- a/SOURCES/sos-bz2036697-ocp-backports.patch +++ b/SOURCES/sos-bz2036697-ocp-backports.patch @@ -1608,159 +1608,6 @@ index 00000000..a4897f19 -- 2.31.1 -From e869bc84c714bfc2249bbcb84e14908049ee42c4 Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Mon, 27 Sep 2021 12:07:08 -0400 -Subject: [PATCH 1/2] [Plugin,utilities] Add sysroot wrapper for os.path.join - -Adds a wrapper for `os.path.join()` which accounts for non-/ sysroots, -like we have done previously for other `os.path` methods. Further -updates `Plugin()` to use this wrapper where appropriate. - -Signed-off-by: Jake Hunsaker ---- - sos/report/plugins/__init__.py | 43 +++++++++++++++++----------------- - sos/utilities.py | 6 +++++ - 2 files changed, 28 insertions(+), 21 deletions(-) - -diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py -index c635b8de..1f84bca4 100644 ---- a/sos/report/plugins/__init__.py -+++ b/sos/report/plugins/__init__.py -@@ -13,7 +13,7 @@ - from sos.utilities import (sos_get_command_output, import_module, grep, - fileobj, tail, is_executable, TIMEOUT_DEFAULT, - path_exists, path_isdir, path_isfile, path_islink, -- listdir) -+ listdir, path_join) - - import os - import glob -@@ -708,19 +708,6 @@ class Plugin(): - def _log_debug(self, msg): - self.soslog.debug(self._format_msg(msg)) - -- def join_sysroot(self, path): -- """Join a given path with the configured sysroot -- -- :param path: The filesystem path that needs to be joined -- :type path: ``str`` -- -- :returns: The joined filesystem path -- :rtype: ``str`` -- """ -- if path[0] == os.sep: -- path = path[1:] -- return os.path.join(self.sysroot, path) -- - def strip_sysroot(self, path): - """Remove the configured sysroot from a filesystem path - -@@ -1176,7 +1163,7 @@ class Plugin(): - - def _get_dest_for_srcpath(self, srcpath): - if self.use_sysroot(): -- srcpath = self.join_sysroot(srcpath) -+ srcpath = self.path_join(srcpath) - for copied in self.copied_files: - if srcpath == copied["srcpath"]: - return copied["dstpath"] -@@ -1284,7 +1271,7 @@ class Plugin(): - forbidden = [forbidden] - - if self.use_sysroot(): -- forbidden = [self.join_sysroot(f) for f in forbidden] -+ forbidden = [self.path_join(f) for f in forbidden] - - for forbid in forbidden: - self._log_info("adding forbidden path '%s'" % forbid) -@@ -1438,7 +1425,7 @@ class Plugin(): - since = self.get_option('since') - - logarchive_pattern = re.compile(r'.*((\.(zip|gz|bz2|xz))|[-.][\d]+)$') -- configfile_pattern = re.compile(r"^%s/*" % self.join_sysroot("etc")) -+ configfile_pattern = re.compile(r"^%s/*" % self.path_join("etc")) - - if not self.test_predicate(pred=pred): - self._log_info("skipped copy spec '%s' due to predicate (%s)" % -@@ -1468,7 +1455,7 @@ class Plugin(): - return False - - if self.use_sysroot(): -- copyspec = self.join_sysroot(copyspec) -+ copyspec = self.path_join(copyspec) - - files = self._expand_copy_spec(copyspec) - -@@ -1683,7 +1670,7 @@ class Plugin(): - if not _dev_ok: - continue - if prepend_path: -- device = os.path.join(prepend_path, device) -+ device = self.path_join(prepend_path, device) - _cmd = cmd % {'dev': device} - self._add_cmd_output(cmd=_cmd, timeout=timeout, - sizelimit=sizelimit, chroot=chroot, -@@ -2592,7 +2579,7 @@ class Plugin(): - if self.path_isfile(path) or self.path_islink(path): - found_paths.append(path) - elif self.path_isdir(path) and self.listdir(path): -- found_paths.extend(__expand(os.path.join(path, '*'))) -+ found_paths.extend(__expand(self.path_join(path, '*'))) - else: - found_paths.append(path) - except PermissionError: -@@ -2608,7 +2595,7 @@ class Plugin(): - if (os.access(copyspec, os.R_OK) and self.path_isdir(copyspec) and - self.listdir(copyspec)): - # the directory exists and is non-empty, recurse through it -- copyspec = os.path.join(copyspec, '*') -+ copyspec = self.path_join(copyspec, '*') - expanded = glob.glob(copyspec, recursive=True) - recursed_files = [] - for _path in expanded: -@@ -2877,6 +2864,20 @@ class Plugin(): - """ - return listdir(path, self.commons['cmdlineopts'].sysroot) - -+ def path_join(self, path, *p): -+ """Helper to call the sos.utilities wrapper that allows the -+ corresponding `os` call to account for sysroot -+ -+ :param path: The leading path passed to os.path.join() -+ :type path: ``str`` -+ -+ :param p: Following path section(s) to be joined with ``path``, -+ an empty parameter will result in a path that ends with -+ a separator -+ :type p: ``str`` -+ """ -+ return path_join(path, *p, sysroot=self.sysroot) -+ - def postproc(self): - """Perform any postprocessing. To be replaced by a plugin if required. - """ -diff --git a/sos/utilities.py b/sos/utilities.py -index c940e066..b7575153 100644 ---- a/sos/utilities.py -+++ b/sos/utilities.py -@@ -242,6 +242,12 @@ def listdir(path, sysroot): - return _os_wrapper(path, sysroot, 'listdir', os) - - -+def path_join(path, *p, sysroot=os.sep): -+ if not path.startswith(sysroot): -+ path = os.path.join(sysroot, path.lstrip(os.sep)) -+ return os.path.join(path, *p) -+ -+ - class AsyncReader(threading.Thread): - """Used to limit command output to a given size without deadlocking - sos. --- -2.31.1 - - From 07d96d52ef69b9f8fe1ef32a1b88089d31c33fe8 Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Mon, 27 Sep 2021 12:28:27 -0400 @@ -3679,697 +3526,6 @@ index e84b52da..1bfa741f 100644 -- 2.31.1 -From 3d064102f8ca6662fd9602512e1cb05cf8746dfd Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Mon, 27 Sep 2021 19:01:16 -0400 -Subject: [PATCH] [Systemd, Policy] Correct InitSystem chrooting when chroot is - needed - -This commit resolves a situation in which `sos` is being run in a -container but the `SystemdInit` InitSystem would not properly load -information from the host, thus causing the `Plugin.is_service*()` -methods to erroneously fail or return `False`. - -Fix this scenario by pulling the `_container_init()` and related logic -to check for a containerized host sysroot out of the Red Hat specific -policy and into the base `LinuxPolicy` class so that the init system can -be initialized with the correct sysroot, which is now used to chroot the -calls to the relevant `systemctl` commands. - -For now, this does impose the use of looking for the `container` env var -(automatically set by docker, podman, and crio regardless of -distribution) and the use of the `HOST` env var to read where the host's -`/` filesystem is mounted within the container. If desired in the -future, this can be changed to allow policy-specific overrides. For now -however, this extends host collection via an sos container for all -distributions currently shipping sos. - -Note that this issue only affected the `InitSystem` abstraction for -loading information about local services, and did not affect init system -related commands called by plugins as part of those collections. - -Signed-off-by: Jake Hunsaker ---- - sos/policies/distros/__init__.py | 28 ++++++++++++++++++++++++++- - sos/policies/distros/redhat.py | 27 +------------------------- - sos/policies/init_systems/__init__.py | 13 +++++++++++-- - sos/policies/init_systems/systemd.py | 7 ++++--- - 4 files changed, 43 insertions(+), 32 deletions(-) - -diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py -index f5b9fd5b01..c33a356a75 100644 ---- a/sos/policies/distros/__init__.py -+++ b/sos/policies/distros/__init__.py -@@ -29,6 +29,10 @@ - except ImportError: - REQUESTS_LOADED = False - -+# Container environment variables for detecting if we're in a container -+ENV_CONTAINER = 'container' -+ENV_HOST_SYSROOT = 'HOST' -+ - - class LinuxPolicy(Policy): - """This policy is meant to be an abc class that provides common -@@ -69,10 +73,17 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True): - probe_runtime=probe_runtime) - self.init_kernel_modules() - -+ # need to set _host_sysroot before PackageManager() -+ if sysroot: -+ self._container_init() -+ self._host_sysroot = sysroot -+ else: -+ sysroot = self._container_init() -+ - if init is not None: - self.init_system = init - elif os.path.isdir("/run/systemd/system/"): -- self.init_system = SystemdInit() -+ self.init_system = SystemdInit(chroot=sysroot) - else: - self.init_system = InitSystem() - -@@ -130,6 +141,21 @@ def get_local_name(self): - def sanitize_filename(self, name): - return re.sub(r"[^-a-z,A-Z.0-9]", "", name) - -+ def _container_init(self): -+ """Check if sos is running in a container and perform container -+ specific initialisation based on ENV_HOST_SYSROOT. -+ """ -+ if ENV_CONTAINER in os.environ: -+ if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: -+ self._in_container = True -+ if ENV_HOST_SYSROOT in os.environ: -+ self._host_sysroot = os.environ[ENV_HOST_SYSROOT] -+ use_sysroot = self._in_container and self._host_sysroot is not None -+ if use_sysroot: -+ host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) -+ self._tmp_dir = host_tmp_dir -+ return self._host_sysroot if use_sysroot else None -+ - def init_kernel_modules(self): - """Obtain a list of loaded kernel modules to reference later for plugin - enablement and SoSPredicate checks -diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py -index b3a84336be..3476e21fb2 100644 ---- a/sos/policies/distros/redhat.py -+++ b/sos/policies/distros/redhat.py -@@ -17,7 +17,7 @@ - from sos.presets.redhat import (RHEL_PRESETS, ATOMIC_PRESETS, RHV, RHEL, - CB, RHOSP, RHOCP, RH_CFME, RH_SATELLITE, - ATOMIC) --from sos.policies.distros import LinuxPolicy -+from sos.policies.distros import LinuxPolicy, ENV_HOST_SYSROOT - from sos.policies.package_managers.rpm import RpmPackageManager - from sos import _sos as _ - -@@ -56,12 +56,6 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, - super(RedHatPolicy, self).__init__(sysroot=sysroot, init=init, - probe_runtime=probe_runtime) - self.usrmove = False -- # need to set _host_sysroot before PackageManager() -- if sysroot: -- self._container_init() -- self._host_sysroot = sysroot -- else: -- sysroot = self._container_init() - - self.package_manager = RpmPackageManager(chroot=sysroot, - remote_exec=remote_exec) -@@ -140,21 +134,6 @@ def transform_path(path): - else: - return files - -- def _container_init(self): -- """Check if sos is running in a container and perform container -- specific initialisation based on ENV_HOST_SYSROOT. -- """ -- if ENV_CONTAINER in os.environ: -- if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: -- self._in_container = True -- if ENV_HOST_SYSROOT in os.environ: -- self._host_sysroot = os.environ[ENV_HOST_SYSROOT] -- use_sysroot = self._in_container and self._host_sysroot is not None -- if use_sysroot: -- host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) -- self._tmp_dir = host_tmp_dir -- return self._host_sysroot if use_sysroot else None -- - def runlevel_by_service(self, name): - from subprocess import Popen, PIPE - ret = [] -@@ -183,10 +162,6 @@ def get_tmp_dir(self, opt_tmp_dir): - return opt_tmp_dir - - --# Container environment variables on Red Hat systems. --ENV_CONTAINER = 'container' --ENV_HOST_SYSROOT = 'HOST' -- - # Legal disclaimer text for Red Hat products - disclaimer_text = """ - Any information provided to %(vendor)s will be treated in \ -diff --git a/sos/policies/init_systems/__init__.py b/sos/policies/init_systems/__init__.py -index dd663e6522..beac44cee3 100644 ---- a/sos/policies/init_systems/__init__.py -+++ b/sos/policies/init_systems/__init__.py -@@ -29,9 +29,14 @@ class InitSystem(): - status of services - :type query_cmd: ``str`` - -+ :param chroot: Location to chroot to for any command execution, i.e. the -+ sysroot if we're running in a container -+ :type chroot: ``str`` or ``None`` -+ - """ - -- def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None): -+ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None, -+ chroot=None): - """Initialize a new InitSystem()""" - - self.services = {} -@@ -39,6 +44,7 @@ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None): - self.init_cmd = init_cmd - self.list_cmd = "%s %s" % (self.init_cmd, list_cmd) or None - self.query_cmd = "%s %s" % (self.init_cmd, query_cmd) or None -+ self.chroot = chroot - - def is_enabled(self, name): - """Check if given service name is enabled -@@ -108,7 +114,10 @@ def _query_service(self, name): - """Query an individual service""" - if self.query_cmd: - try: -- return sos_get_command_output("%s %s" % (self.query_cmd, name)) -+ return sos_get_command_output( -+ "%s %s" % (self.query_cmd, name), -+ chroot=self.chroot -+ ) - except Exception: - return None - return None -diff --git a/sos/policies/init_systems/systemd.py b/sos/policies/init_systems/systemd.py -index 1b138f97b3..76dc57e27f 100644 ---- a/sos/policies/init_systems/systemd.py -+++ b/sos/policies/init_systems/systemd.py -@@ -15,11 +15,12 @@ - class SystemdInit(InitSystem): - """InitSystem abstraction for SystemD systems""" - -- def __init__(self): -+ def __init__(self, chroot=None): - super(SystemdInit, self).__init__( - init_cmd='systemctl', - list_cmd='list-unit-files --type=service', -- query_cmd='status' -+ query_cmd='status', -+ chroot=chroot - ) - self.load_all_services() - -@@ -30,7 +31,7 @@ def parse_query(self, output): - return 'unknown' - - def load_all_services(self): -- svcs = shell_out(self.list_cmd).splitlines()[1:] -+ svcs = shell_out(self.list_cmd, chroot=self.chroot).splitlines()[1:] - for line in svcs: - try: - name = line.split('.service')[0] --- -2.31.1 - -From 9596473d1779b9c48e9923c220aaf2b8d9b3bebf Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Thu, 18 Nov 2021 13:17:14 -0500 -Subject: [PATCH] [global] Align sysroot determination and usage across sos - -The determination of sysroot - being automatic, user-specified, or -controlled via environment variables in a container - has gotten muddied -over time. This has resulted in different parts of the project; -`Policy`, `Plugin`, `SoSComponent`, etc... to not always be in sync when -sysroot is not `/`, thus causing varying and unexpected/unintended -behavior. - -Fix this by only determining sysroot within `Policy()` initialization, -and then using that determination across all aspects of the project that -use or reference sysroot. - -This results in several changes: - -- `PackageManager()` will now (again) correctly reference host package - lists when sos is run in a container. - -- `ContainerRuntime()` is now able to activate when sos is running in a - container. - -- Plugins will now properly use sysroot for _all_ plugin enablement - triggers. - -- Plugins, Policy, and SoSComponents now all reference the - `self.sysroot` variable, rather than changing between `sysroot`. -`_host_sysroot`, and `commons['sysroot']`. `_host_sysroot` has been -removed from `Policy`. - -Signed-off-by: Jake Hunsaker ---- - sos/archive.py | 2 +- - sos/component.py | 2 +- - sos/policies/__init__.py | 11 +---------- - sos/policies/distros/__init__.py | 33 +++++++++++++++++++------------ - sos/policies/distros/debian.py | 2 +- - sos/policies/distros/redhat.py | 3 +-- - sos/policies/runtimes/__init__.py | 15 +++++++++----- - sos/policies/runtimes/docker.py | 4 ++-- - sos/report/__init__.py | 6 ++---- - sos/report/plugins/__init__.py | 22 +++++++++++---------- - sos/report/plugins/unpackaged.py | 7 ++++--- - sos/utilities.py | 13 ++++++++---- - 12 files changed, 64 insertions(+), 56 deletions(-) - -diff --git a/sos/archive.py b/sos/archive.py -index b02b2475..e3c68b77 100644 ---- a/sos/archive.py -+++ b/sos/archive.py -@@ -153,7 +153,7 @@ class FileCacheArchive(Archive): - return (os.path.join(self._archive_root, name)) - - def join_sysroot(self, path): -- if path.startswith(self.sysroot): -+ if not self.sysroot or path.startswith(self.sysroot): - return path - if path[0] == os.sep: - path = path[1:] -diff --git a/sos/component.py b/sos/component.py -index 5ac6e47f..dba0aabf 100644 ---- a/sos/component.py -+++ b/sos/component.py -@@ -109,7 +109,7 @@ class SoSComponent(): - try: - import sos.policies - self.policy = sos.policies.load(sysroot=self.opts.sysroot) -- self.sysroot = self.policy.host_sysroot() -+ self.sysroot = self.policy.sysroot - except KeyboardInterrupt: - self._exit(0) - self._is_root = self.policy.is_root() -diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py -index fb8db1d7..ef9188de 100644 ---- a/sos/policies/__init__.py -+++ b/sos/policies/__init__.py -@@ -110,7 +110,6 @@ any third party. - presets = {"": PresetDefaults()} - presets_path = PRESETS_PATH - _in_container = False -- _host_sysroot = '/' - - def __init__(self, sysroot=None, probe_runtime=True): - """Subclasses that choose to override this initializer should call -@@ -124,7 +123,7 @@ any third party. - self.package_manager = PackageManager() - self.valid_subclasses = [IndependentPlugin] - self.set_exec_path() -- self._host_sysroot = sysroot -+ self.sysroot = sysroot - self.register_presets(GENERIC_PRESETS) - - def check(self, remote=''): -@@ -177,14 +176,6 @@ any third party. - """ - return self._in_container - -- def host_sysroot(self): -- """Get the host's default sysroot -- -- :returns: Host sysroot -- :rtype: ``str`` or ``None`` -- """ -- return self._host_sysroot -- - def dist_version(self): - """ - Return the OS version -diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py -index 7bdc81b8..c69fc1e7 100644 ---- a/sos/policies/distros/__init__.py -+++ b/sos/policies/distros/__init__.py -@@ -71,19 +71,18 @@ class LinuxPolicy(Policy): - def __init__(self, sysroot=None, init=None, probe_runtime=True): - super(LinuxPolicy, self).__init__(sysroot=sysroot, - probe_runtime=probe_runtime) -- self.init_kernel_modules() - -- # need to set _host_sysroot before PackageManager() - if sysroot: -- self._container_init() -- self._host_sysroot = sysroot -+ self.sysroot = sysroot - else: -- sysroot = self._container_init() -+ self.sysroot = self._container_init() -+ -+ self.init_kernel_modules() - - if init is not None: - self.init_system = init - elif os.path.isdir("/run/systemd/system/"): -- self.init_system = SystemdInit(chroot=sysroot) -+ self.init_system = SystemdInit(chroot=self.sysroot) - else: - self.init_system = InitSystem() - -@@ -149,27 +148,30 @@ class LinuxPolicy(Policy): - if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: - self._in_container = True - if ENV_HOST_SYSROOT in os.environ: -- self._host_sysroot = os.environ[ENV_HOST_SYSROOT] -- use_sysroot = self._in_container and self._host_sysroot is not None -+ _host_sysroot = os.environ[ENV_HOST_SYSROOT] -+ use_sysroot = self._in_container and _host_sysroot is not None - if use_sysroot: -- host_tmp_dir = os.path.abspath(self._host_sysroot + self._tmp_dir) -+ host_tmp_dir = os.path.abspath(_host_sysroot + self._tmp_dir) - self._tmp_dir = host_tmp_dir -- return self._host_sysroot if use_sysroot else None -+ return _host_sysroot if use_sysroot else None - - def init_kernel_modules(self): - """Obtain a list of loaded kernel modules to reference later for plugin - enablement and SoSPredicate checks - """ - self.kernel_mods = [] -+ release = os.uname().release - - # first load modules from lsmod -- lines = shell_out("lsmod", timeout=0).splitlines() -+ lines = shell_out("lsmod", timeout=0, chroot=self.sysroot).splitlines() - self.kernel_mods.extend([ - line.split()[0].strip() for line in lines[1:] - ]) - - # next, include kernel builtins -- builtins = "/usr/lib/modules/%s/modules.builtin" % os.uname().release -+ builtins = self.join_sysroot( -+ "/usr/lib/modules/%s/modules.builtin" % release -+ ) - try: - with open(builtins, "r") as mfile: - for line in mfile: -@@ -186,7 +188,7 @@ class LinuxPolicy(Policy): - 'dm_mod': 'CONFIG_BLK_DEV_DM' - } - -- booted_config = "/boot/config-%s" % os.uname().release -+ booted_config = self.join_sysroot("/boot/config-%s" % release) - kconfigs = [] - try: - with open(booted_config, "r") as kfile: -@@ -200,6 +202,11 @@ class LinuxPolicy(Policy): - if config_strings[builtin] in kconfigs: - self.kernel_mods.append(builtin) - -+ def join_sysroot(self, path): -+ if self.sysroot and self.sysroot != '/': -+ path = os.path.join(self.sysroot, path.lstrip('/')) -+ return path -+ - def pre_work(self): - # this method will be called before the gathering begins - -diff --git a/sos/policies/distros/debian.py b/sos/policies/distros/debian.py -index 95b389a6..639fd5eb 100644 ---- a/sos/policies/distros/debian.py -+++ b/sos/policies/distros/debian.py -@@ -27,7 +27,7 @@ class DebianPolicy(LinuxPolicy): - remote_exec=None): - super(DebianPolicy, self).__init__(sysroot=sysroot, init=init, - probe_runtime=probe_runtime) -- self.package_manager = DpkgPackageManager(chroot=sysroot, -+ self.package_manager = DpkgPackageManager(chroot=self.sysroot, - remote_exec=remote_exec) - self.valid_subclasses += [DebianPlugin] - -diff --git a/sos/policies/distros/redhat.py b/sos/policies/distros/redhat.py -index eb442407..4b14abaf 100644 ---- a/sos/policies/distros/redhat.py -+++ b/sos/policies/distros/redhat.py -@@ -42,7 +42,6 @@ class RedHatPolicy(LinuxPolicy): - _redhat_release = '/etc/redhat-release' - _tmp_dir = "/var/tmp" - _in_container = False -- _host_sysroot = '/' - default_scl_prefix = '/opt/rh' - name_pattern = 'friendly' - upload_url = None -@@ -57,7 +56,7 @@ class RedHatPolicy(LinuxPolicy): - probe_runtime=probe_runtime) - self.usrmove = False - -- self.package_manager = RpmPackageManager(chroot=sysroot, -+ self.package_manager = RpmPackageManager(chroot=self.sysroot, - remote_exec=remote_exec) - - self.valid_subclasses += [RedHatPlugin] -diff --git a/sos/policies/runtimes/__init__.py b/sos/policies/runtimes/__init__.py -index f28d6a1d..2e60ad23 100644 ---- a/sos/policies/runtimes/__init__.py -+++ b/sos/policies/runtimes/__init__.py -@@ -64,7 +64,7 @@ class ContainerRuntime(): - :returns: ``True`` if the runtime is active, else ``False`` - :rtype: ``bool`` - """ -- if is_executable(self.binary): -+ if is_executable(self.binary, self.policy.sysroot): - self.active = True - return True - return False -@@ -78,7 +78,7 @@ class ContainerRuntime(): - containers = [] - _cmd = "%s ps %s" % (self.binary, '-a' if get_all else '') - if self.active: -- out = sos_get_command_output(_cmd) -+ out = sos_get_command_output(_cmd, chroot=self.policy.sysroot) - if out['status'] == 0: - for ent in out['output'].splitlines()[1:]: - ent = ent.split() -@@ -112,8 +112,10 @@ class ContainerRuntime(): - images = [] - fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}' - if self.active: -- out = sos_get_command_output("%s images --format '%s'" -- % (self.binary, fmt)) -+ out = sos_get_command_output( -+ "%s images --format '%s'" % (self.binary, fmt), -+ chroot=self.policy.sysroot -+ ) - if out['status'] == 0: - for ent in out['output'].splitlines(): - ent = ent.split() -@@ -129,7 +131,10 @@ class ContainerRuntime(): - """ - vols = [] - if self.active: -- out = sos_get_command_output("%s volume ls" % self.binary) -+ out = sos_get_command_output( -+ "%s volume ls" % self.binary, -+ chroot=self.policy.sysroot -+ ) - if out['status'] == 0: - for ent in out['output'].splitlines()[1:]: - ent = ent.split() -diff --git a/sos/policies/runtimes/docker.py b/sos/policies/runtimes/docker.py -index 759dfaf6..e81f580e 100644 ---- a/sos/policies/runtimes/docker.py -+++ b/sos/policies/runtimes/docker.py -@@ -18,9 +18,9 @@ class DockerContainerRuntime(ContainerRuntime): - name = 'docker' - binary = 'docker' - -- def check_is_active(self): -+ def check_is_active(self, sysroot=None): - # the daemon must be running -- if (is_executable('docker') and -+ if (is_executable('docker', sysroot) and - (self.policy.init_system.is_running('docker') or - self.policy.init_system.is_running('snap.docker.dockerd'))): - self.active = True -diff --git a/sos/report/__init__.py b/sos/report/__init__.py -index a4c92acc..a6c72778 100644 ---- a/sos/report/__init__.py -+++ b/sos/report/__init__.py -@@ -173,14 +173,12 @@ class SoSReport(SoSComponent): - self._set_directories() - - msg = "default" -- host_sysroot = self.policy.host_sysroot() -+ self.sysroot = self.policy.sysroot - # set alternate system root directory - if self.opts.sysroot: - msg = "cmdline" -- self.sysroot = self.opts.sysroot -- elif self.policy.in_container() and host_sysroot != os.sep: -+ elif self.policy.in_container() and self.sysroot != os.sep: - msg = "policy" -- self.sysroot = host_sysroot - self.soslog.debug("set sysroot to '%s' (%s)" % (self.sysroot, msg)) - - if self.opts.chroot not in chroot_modes: -diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py -index 46028bb1..e180ae17 100644 ---- a/sos/report/plugins/__init__.py -+++ b/sos/report/plugins/__init__.py -@@ -724,7 +724,7 @@ class Plugin(): - """ - if not self.use_sysroot(): - return path -- if path.startswith(self.sysroot): -+ if self.sysroot and path.startswith(self.sysroot): - return path[len(self.sysroot):] - return path - -@@ -743,8 +743,10 @@ class Plugin(): - ``False`` - :rtype: ``bool`` - """ -- paths = [self.sysroot, self.archive.get_tmp_dir()] -- return os.path.commonprefix(paths) == self.sysroot -+ # if sysroot is still None, that implies '/' -+ _sysroot = self.sysroot or '/' -+ paths = [_sysroot, self.archive.get_tmp_dir()] -+ return os.path.commonprefix(paths) == _sysroot - - def is_installed(self, package_name): - """Is the package $package_name installed? -@@ -2621,7 +2623,7 @@ class Plugin(): - return list(set(expanded)) - - def _collect_copy_specs(self): -- for path in self.copy_paths: -+ for path in sorted(self.copy_paths, reverse=True): - self._log_info("collecting path '%s'" % path) - self._do_copy_path(path) - self.generate_copyspec_tags() -@@ -2749,7 +2751,7 @@ class Plugin(): - - return ((any(self.path_exists(fname) for fname in files) or - any(self.is_installed(pkg) for pkg in packages) or -- any(is_executable(cmd) for cmd in commands) or -+ any(is_executable(cmd, self.sysroot) for cmd in commands) or - any(self.is_module_loaded(mod) for mod in self.kernel_mods) or - any(self.is_service(svc) for svc in services) or - any(self.container_exists(cntr) for cntr in containers)) and -@@ -2817,7 +2819,7 @@ class Plugin(): - :returns: True if the path exists in sysroot, else False - :rtype: ``bool`` - """ -- return path_exists(path, self.commons['cmdlineopts'].sysroot) -+ return path_exists(path, self.sysroot) - - def path_isdir(self, path): - """Helper to call the sos.utilities wrapper that allows the -@@ -2830,7 +2832,7 @@ class Plugin(): - :returns: True if the path is a dir, else False - :rtype: ``bool`` - """ -- return path_isdir(path, self.commons['cmdlineopts'].sysroot) -+ return path_isdir(path, self.sysroot) - - def path_isfile(self, path): - """Helper to call the sos.utilities wrapper that allows the -@@ -2843,7 +2845,7 @@ class Plugin(): - :returns: True if the path is a file, else False - :rtype: ``bool`` - """ -- return path_isfile(path, self.commons['cmdlineopts'].sysroot) -+ return path_isfile(path, self.sysroot) - - def path_islink(self, path): - """Helper to call the sos.utilities wrapper that allows the -@@ -2856,7 +2858,7 @@ class Plugin(): - :returns: True if the path is a link, else False - :rtype: ``bool`` - """ -- return path_islink(path, self.commons['cmdlineopts'].sysroot) -+ return path_islink(path, self.sysroot) - - def listdir(self, path): - """Helper to call the sos.utilities wrapper that allows the -@@ -2869,7 +2871,7 @@ class Plugin(): - :returns: Contents of path, if it is a directory - :rtype: ``list`` - """ -- return listdir(path, self.commons['cmdlineopts'].sysroot) -+ return listdir(path, self.sysroot) - - def path_join(self, path, *p): - """Helper to call the sos.utilities wrapper that allows the -diff --git a/sos/report/plugins/unpackaged.py b/sos/report/plugins/unpackaged.py -index 772b1d1f..24203c4b 100644 ---- a/sos/report/plugins/unpackaged.py -+++ b/sos/report/plugins/unpackaged.py -@@ -58,10 +58,11 @@ class Unpackaged(Plugin, RedHatPlugin): - """ - expanded = [] - for f in files: -- if self.path_islink(f): -- expanded.append("{} -> {}".format(f, os.readlink(f))) -+ fp = self.path_join(f) -+ if self.path_islink(fp): -+ expanded.append("{} -> {}".format(fp, os.readlink(fp))) - else: -- expanded.append(f) -+ expanded.append(fp) - return expanded - - # Check command predicate to avoid costly processing -diff --git a/sos/utilities.py b/sos/utilities.py -index b7575153..d6630933 100644 ---- a/sos/utilities.py -+++ b/sos/utilities.py -@@ -96,11 +96,15 @@ def grep(pattern, *files_or_paths): - return matches - - --def is_executable(command): -+def is_executable(command, sysroot=None): - """Returns if a command matches an executable on the PATH""" - - paths = os.environ.get("PATH", "").split(os.path.pathsep) - candidates = [command] + [os.path.join(p, command) for p in paths] -+ if sysroot: -+ candidates += [ -+ os.path.join(sysroot, c.lstrip('/')) for c in candidates -+ ] - return any(os.access(path, os.X_OK) for path in candidates) - - -@@ -216,8 +220,9 @@ def get_human_readable(size, precision=2): - - - def _os_wrapper(path, sysroot, method, module=os.path): -- if sysroot not in [None, '/']: -- path = os.path.join(sysroot, path.lstrip('/')) -+ if sysroot and sysroot != os.sep: -+ if not path.startswith(sysroot): -+ path = os.path.join(sysroot, path.lstrip('/')) - _meth = getattr(module, method) - return _meth(path) - -@@ -243,7 +248,7 @@ def listdir(path, sysroot): - - - def path_join(path, *p, sysroot=os.sep): -- if not path.startswith(sysroot): -+ if sysroot and not path.startswith(sysroot): - path = os.path.join(sysroot, path.lstrip(os.sep)) - return os.path.join(path, *p) - --- -2.31.1 - From 8bf602108f75db10e449eff5e2266c6466504086 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 2 Dec 2021 16:30:44 +0100 @@ -5868,20 +5024,122 @@ index cb20772fd..b59eade9a 100644 def test_ip_parser_valid_ipv4_line(self): line = 'foobar foo 10.0.0.1/24 barfoo bar' -From: Pavel Moravec -Subject: downstream-only patch to allow container_runtime change on 4.2 -sos cluster/collector already, as any 4.2 released version will support -it. -diff -rup a/sos/collector/sosnode.py b/sos/collector/sosnode.py +From 2ae16e0245e1b01b8547e507abb69c11871a8467 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 21 Feb 2022 14:37:09 -0500 +Subject: [PATCH] [sosnode] Handle downstream versioning for runtime option + check + +First, adds parsing and formatting for an sos installation's release +version according to the loaded package manager for that node. + +Adds a fallback version check for 4.2-13 for RHEL downstreams that +backport the `container-runtime` option into sos-4.2. + +Carry this in upstream to account for use cases where a workstation used +to run `collect` from may be from a different stream than those used by +cluster nodes. + +Signed-off-by: Jake Hunsaker +--- + sos/collector/sosnode.py | 60 ++++++++++++++++++++++++++++++++++------ + 1 file changed, 51 insertions(+), 9 deletions(-) + +diff --git a/sos/collector/sosnode.py b/sos/collector/sosnode.py +index 7bbe0cd1..d9b998b0 100644 --- a/sos/collector/sosnode.py +++ b/sos/collector/sosnode.py -@@ -586,8 +586,6 @@ class SosNode(): - if self.opts.cmd_timeout: +@@ -275,21 +275,34 @@ class SosNode(): + def _load_sos_info(self): + """Queries the node for information about the installed version of sos + """ ++ ver = None ++ rel = None + if self.host.container_version_command is None: + pkg = self.host.package_manager.pkg_version(self.host.sos_pkg_name) + if pkg is not None: + ver = '.'.join(pkg['version']) +- self.sos_info['version'] = ver ++ if pkg['release']: ++ rel = pkg['release'] ++ + else: + # use the containerized policy's command + pkgs = self.run_command(self.host.container_version_command, + use_container=True, need_root=True) + if pkgs['status'] == 0: +- ver = pkgs['output'].strip().split('-')[1] +- if ver: +- self.sos_info['version'] = ver +- else: +- self.sos_info['version'] = None ++ _, ver, rel = pkgs['output'].strip().split('-') ++ ++ if ver: ++ if len(ver.split('.')) == 2: ++ # safeguard against maintenance releases throwing off the ++ # comparison by LooseVersion ++ ver += '.0' ++ try: ++ ver += '-%s' % rel.split('.')[0] ++ except Exception as err: ++ self.log_debug("Unable to fully parse sos release: %s" % err) ++ ++ self.sos_info['version'] = ver ++ + if self.sos_info['version']: + self.log_info('sos version is %s' % self.sos_info['version']) + else: +@@ -381,9 +394,37 @@ class SosNode(): + """Checks to see if the sos installation on the node is AT LEAST the + given ver. This means that if the installed version is greater than + ver, this will still return True ++ ++ :param ver: Version number we are trying to verify is installed ++ :type ver: ``str`` ++ ++ :returns: True if installed version is at least ``ver``, else False ++ :rtype: ``bool`` + """ +- return self.sos_info['version'] is not None and \ +- LooseVersion(self.sos_info['version']) >= ver ++ def _format_version(ver): ++ # format the version we're checking to a standard form of X.Y.Z-R ++ try: ++ _fver = ver.split('-')[0] ++ _rel = '' ++ if '-' in ver: ++ _rel = '-' + ver.split('-')[-1].split('.')[0] ++ if len(_fver.split('.')) == 2: ++ _fver += '.0' ++ ++ return _fver + _rel ++ except Exception as err: ++ self.log_debug("Unable to format '%s': %s" % (ver, err)) ++ return ver ++ ++ _ver = _format_version(ver) ++ ++ try: ++ _node_ver = LooseVersion(self.sos_info['version']) ++ _test_ver = LooseVersion(_ver) ++ return _node_ver >= _test_ver ++ except Exception as err: ++ self.log_error("Error checking sos version: %s" % err) ++ return False + + def is_installed(self, pkg): + """Checks if a given package is installed on the node""" +@@ -587,7 +628,8 @@ class SosNode(): sos_opts.append('--cmd-timeout=%s' % quote(str(self.opts.cmd_timeout))) -- + - if self.check_sos_version('4.3'): ++ # handle downstream versions that backported this option ++ if self.check_sos_version('4.3') or self.check_sos_version('4.2-13'): if self.opts.container_runtime != 'auto': sos_opts.append( "--container-runtime=%s" % self.opts.container_runtime +-- +2.34.1 diff --git a/SOURCES/sos-bz2042966-ovn-proper-package-enablement.patch b/SOURCES/sos-bz2042966-ovn-proper-package-enablement.patch index 945eda9..16c48c4 100644 --- a/SOURCES/sos-bz2042966-ovn-proper-package-enablement.patch +++ b/SOURCES/sos-bz2042966-ovn-proper-package-enablement.patch @@ -43,3 +43,210 @@ index 78604a15a..25c38cccc 100644 class DebianOVNHost(OVNHost, DebianPlugin, UbuntuPlugin): +From 21fc376d97a5f74743e2b7cf7069349e874b979e Mon Sep 17 00:00:00 2001 +From: Hemanth Nakkina +Date: Fri, 4 Feb 2022 07:57:59 +0530 +Subject: [PATCH] [ovn-central] collect NB/SB ovsdb-server cluster status + +Add commands to collect cluster status of Northbound and +Southbound ovsdb servers. + +Resolves: #2840 + +Signed-off-by: Hemanth Nakkina hemanth.nakkina@canonical.com +--- + sos/report/plugins/ovn_central.py | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/sos/report/plugins/ovn_central.py b/sos/report/plugins/ovn_central.py +index 0f947d4c5..2f0438df3 100644 +--- a/sos/report/plugins/ovn_central.py ++++ b/sos/report/plugins/ovn_central.py +@@ -84,6 +84,14 @@ def setup(self): + else: + self.add_copy_spec("/var/log/ovn/*.log") + ++ # ovsdb nb/sb cluster status commands ++ ovsdb_cmds = [ ++ 'ovs-appctl -t {} cluster/status OVN_Northbound'.format( ++ self.ovn_nbdb_sock_path), ++ 'ovs-appctl -t {} cluster/status OVN_Southbound'.format( ++ self.ovn_sbdb_sock_path), ++ ] ++ + # Some user-friendly versions of DB output + nbctl_cmds = [ + 'ovn-nbctl show', +@@ -109,7 +117,8 @@ def setup(self): + + self.add_database_output(nb_tables, nbctl_cmds, 'ovn-nbctl') + +- cmds = nbctl_cmds ++ cmds = ovsdb_cmds ++ cmds += nbctl_cmds + + # Can only run sbdb commands if we are the leader + co = {'cmd': "ovs-appctl -t {} cluster/status OVN_Southbound". +@@ -148,10 +157,12 @@ def setup(self): + class RedHatOVNCentral(OVNCentral, RedHatPlugin): + + packages = ('openvswitch-ovn-central', 'ovn.*-central', ) ++ ovn_nbdb_sock_path = '/var/run/openvswitch/ovnnb_db.ctl' + ovn_sbdb_sock_path = '/var/run/openvswitch/ovnsb_db.ctl' + + + class DebianOVNCentral(OVNCentral, DebianPlugin, UbuntuPlugin): + + packages = ('ovn-central', ) ++ ovn_nbdb_sock_path = '/var/run/ovn/ovnnb_db.ctl' + ovn_sbdb_sock_path = '/var/run/ovn/ovnsb_db.ctl' +From d0f9d507b0ec63c9e8f3e5d7b6507d9d0f97c038 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 15 Feb 2022 16:24:47 -0500 +Subject: [PATCH] [runtimes] Allow container IDs to be used with + `container_exists()` + +As container runtimes can interchange container names and container IDs, +sos should also allow the use of container IDs when checking for the +presence of a given container. + +In particular, this change unblocks the use of `Plugin.exec_cmd()` when +used in conjunction with `Plugin.get_container_by_name()` to pick a +container based on a provided regex that the container name may match. + +Related: #2856 + +Signed-off-by: Jake Hunsaker +--- + sos/policies/runtimes/__init__.py | 17 +++++++++++++++++ + sos/report/plugins/__init__.py | 6 +++--- + 2 files changed, 20 insertions(+), 3 deletions(-) + +diff --git a/sos/policies/runtimes/__init__.py b/sos/policies/runtimes/__init__.py +index 5ac673544..d28373496 100644 +--- a/sos/policies/runtimes/__init__.py ++++ b/sos/policies/runtimes/__init__.py +@@ -147,6 +147,23 @@ def get_volumes(self): + vols.append(ent[-1]) + return vols + ++ def container_exists(self, container): ++ """Check if a given container ID or name exists on the system from the ++ perspective of the container runtime. ++ ++ Note that this will only check _running_ containers ++ ++ :param container: The name or ID of the container ++ :type container: ``str`` ++ ++ :returns: True if the container exists, else False ++ :rtype: ``bool`` ++ """ ++ for _contup in self.containers: ++ if container in _contup: ++ return True ++ return False ++ + def fmt_container_cmd(self, container, cmd, quotecmd): + """Format a command to run inside a container using the runtime + +diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py +index 2988be089..cc5cb65bc 100644 +--- a/sos/report/plugins/__init__.py ++++ b/sos/report/plugins/__init__.py +@@ -2593,7 +2593,7 @@ def container_exists(self, name): + """If a container runtime is present, check to see if a container with + a given name is currently running + +- :param name: The name of the container to check presence of ++ :param name: The name or ID of the container to check presence of + :type name: ``str`` + + :returns: ``True`` if `name` exists, else ``False`` +@@ -2601,8 +2601,8 @@ def container_exists(self, name): + """ + _runtime = self._get_container_runtime() + if _runtime is not None: +- con = _runtime.get_container_by_name(name) +- return con is not None ++ return (_runtime.container_exists(name) or ++ _runtime.get_container_by_name(name) is not None) + return False + + def get_all_containers_by_regex(self, regex, get_all=False): + +From de9b020a72d1ceda39587db4c6d5acf72cd90da2 Mon Sep 17 00:00:00 2001 +From: Fernando Royo +Date: Tue, 15 Feb 2022 10:00:38 +0100 +Subject: [PATCH] [ovn_central] Rename container responsable of Red Hat + ovn_central plugin + +ovn_central plugin is running by container with +name 'ovn-dbs-bundle*', a typo has been identified and +this cause plugin ovn_central not enabled by default as it +does not recognize any container responsible of this. + +This patch fix this container name match, searching schema db +keeping backward compatibility with openvswitch. +--- + sos/report/plugins/ovn_central.py | 23 ++++++++++++----------- + 1 file changed, 12 insertions(+), 11 deletions(-) + +diff --git a/sos/report/plugins/ovn_central.py b/sos/report/plugins/ovn_central.py +index 2f0438df..2f34bff0 100644 +--- a/sos/report/plugins/ovn_central.py ++++ b/sos/report/plugins/ovn_central.py +@@ -24,7 +24,7 @@ class OVNCentral(Plugin): + short_desc = 'OVN Northd' + plugin_name = "ovn_central" + profiles = ('network', 'virt') +- containers = ('ovs-db-bundle.*',) ++ containers = ('ovn-dbs-bundle.*',) + + def get_tables_from_schema(self, filename, skip=[]): + if self._container_name: +@@ -66,7 +66,7 @@ class OVNCentral(Plugin): + cmds.append('%s list %s' % (ovn_cmd, table)) + + def setup(self): +- self._container_name = self.get_container_by_name('ovs-dbs-bundle.*') ++ self._container_name = self.get_container_by_name(self.containers[0]) + + ovs_rundir = os.environ.get('OVS_RUNDIR') + for pidfile in ['ovnnb_db.pid', 'ovnsb_db.pid', 'ovn-northd.pid']: +@@ -110,12 +110,11 @@ class OVNCentral(Plugin): + 'ovn-sbctl get-connection', + ] + +- schema_dir = '/usr/share/openvswitch' +- +- nb_tables = self.get_tables_from_schema(self.path_join( +- schema_dir, 'ovn-nb.ovsschema')) +- +- self.add_database_output(nb_tables, nbctl_cmds, 'ovn-nbctl') ++ # backward compatibility ++ for path in ['/usr/share/openvswitch', '/usr/share/ovn']: ++ nb_tables = self.get_tables_from_schema(self.path_join( ++ path, 'ovn-nb.ovsschema')) ++ self.add_database_output(nb_tables, nbctl_cmds, 'ovn-nbctl') + + cmds = ovsdb_cmds + cmds += nbctl_cmds +@@ -125,9 +124,11 @@ class OVNCentral(Plugin): + format(self.ovn_sbdb_sock_path), + "output": "Leader: self"} + if self.test_predicate(self, pred=SoSPredicate(self, cmd_outputs=co)): +- sb_tables = self.get_tables_from_schema(self.path_join( +- schema_dir, 'ovn-sb.ovsschema'), ['Logical_Flow']) +- self.add_database_output(sb_tables, sbctl_cmds, 'ovn-sbctl') ++ # backward compatibility ++ for path in ['/usr/share/openvswitch', '/usr/share/ovn']: ++ sb_tables = self.get_tables_from_schema(self.path_join( ++ path, 'ovn-sb.ovsschema'), ['Logical_Flow']) ++ self.add_database_output(sb_tables, sbctl_cmds, 'ovn-sbctl') + cmds += sbctl_cmds + + # If OVN is containerized, we need to run the above commands inside +-- +2.34.1 + diff --git a/SOURCES/sos-bz2054882-plugopt-logging-effective-opts.patch b/SOURCES/sos-bz2054882-plugopt-logging-effective-opts.patch new file mode 100644 index 0000000..f8e7ed3 --- /dev/null +++ b/SOURCES/sos-bz2054882-plugopt-logging-effective-opts.patch @@ -0,0 +1,94 @@ +From 5824cd5d3bddf39e0382d568419e2453abc93d8a Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 30 Aug 2021 15:09:07 -0400 +Subject: [PATCH] [options] Fix logging on plugopts in effective sos command + +First, provide a special-case handling for plugin options specified in +sos.conf in `SoSOptions.to_args().has_value()` that allows for plugin +options to be included in the "effective options now" log message. + +Second, move the logging of said message (and thus the merging of +preset options, if used), to being _prior_ to the loading of plugin +options. + +Combined, plugin options specified in sos.conf will now be logged +properly and this logging will occur before we set (and log the setting +of) those options. + +Resolves: #2663 + +Signed-off-by: Jake Hunsaker +--- + sos/options.py | 2 ++ + sos/report/__init__.py | 30 ++++++++++++++++-------------- + 2 files changed, 18 insertions(+), 14 deletions(-) + +diff --git a/sos/options.py b/sos/options.py +index a014a022..7bea3ffc 100644 +--- a/sos/options.py ++++ b/sos/options.py +@@ -281,6 +281,8 @@ class SoSOptions(): + null_values = ("False", "None", "[]", '""', "''", "0") + if not value or value in null_values: + return False ++ if name == 'plugopts' and value: ++ return True + if name in self.arg_defaults: + if str(value) == str(self.arg_defaults[name]): + return False +diff --git a/sos/report/__init__.py b/sos/report/__init__.py +index b0159e5b..82484f1d 100644 +--- a/sos/report/__init__.py ++++ b/sos/report/__init__.py +@@ -925,20 +925,6 @@ class SoSReport(SoSComponent): + self._exit(1) + + def setup(self): +- # Log command line options +- msg = "[%s:%s] executing 'sos %s'" +- self.soslog.info(msg % (__name__, "setup", " ".join(self.cmdline))) +- +- # Log active preset defaults +- preset_args = self.preset.opts.to_args() +- msg = ("[%s:%s] using '%s' preset defaults (%s)" % +- (__name__, "setup", self.preset.name, " ".join(preset_args))) +- self.soslog.info(msg) +- +- # Log effective options after applying preset defaults +- self.soslog.info("[%s:%s] effective options now: %s" % +- (__name__, "setup", " ".join(self.opts.to_args()))) +- + self.ui_log.info(_(" Setting up plugins ...")) + for plugname, plug in self.loaded_plugins: + try: +@@ -1386,11 +1372,27 @@ class SoSReport(SoSComponent): + self.report_md.add_list('disabled_plugins', self.opts.skip_plugins) + self.report_md.add_section('plugins') + ++ def _merge_preset_options(self): ++ # Log command line options ++ msg = "[%s:%s] executing 'sos %s'" ++ self.soslog.info(msg % (__name__, "setup", " ".join(self.cmdline))) ++ ++ # Log active preset defaults ++ preset_args = self.preset.opts.to_args() ++ msg = ("[%s:%s] using '%s' preset defaults (%s)" % ++ (__name__, "setup", self.preset.name, " ".join(preset_args))) ++ self.soslog.info(msg) ++ ++ # Log effective options after applying preset defaults ++ self.soslog.info("[%s:%s] effective options now: %s" % ++ (__name__, "setup", " ".join(self.opts.to_args()))) ++ + def execute(self): + try: + self.policy.set_commons(self.get_commons()) + self.load_plugins() + self._set_all_options() ++ self._merge_preset_options() + self._set_tunables() + self._check_for_unknown_plugins() + self._set_plugin_options() +-- +2.34.1 + diff --git a/SOURCES/sos-bz2055547-honour-plugins-timeout-hardcoded.patch b/SOURCES/sos-bz2055547-honour-plugins-timeout-hardcoded.patch new file mode 100644 index 0000000..3adde40 --- /dev/null +++ b/SOURCES/sos-bz2055547-honour-plugins-timeout-hardcoded.patch @@ -0,0 +1,39 @@ +From 7069e99d1c5c443f96a98a7ed6db67fa14683e67 Mon Sep 17 00:00:00 2001 +From: Pavel Moravec +Date: Thu, 17 Feb 2022 09:14:15 +0100 +Subject: [PATCH] [report] Honor plugins' hardcoded plugin_timeout + +Currently, plugin's plugin_timeout hardcoded default is superseded by +whatever --plugin-timeout value, even when this option is not used and +we eval it to TIMEOUT_DEFAULT. + +In this case of not setting --plugin-timeout either -k plugin.timeout, +honour plugin's plugin_timeout instead. + +Resolves: #2863 +Closes: #2864 + +Signed-off-by: Pavel Moravec +--- + sos/report/plugins/__init__.py | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py +index cc5cb65b..336b4d22 100644 +--- a/sos/report/plugins/__init__.py ++++ b/sos/report/plugins/__init__.py +@@ -636,7 +636,10 @@ class Plugin(): + if opt_timeout is None: + _timeout = own_timeout + elif opt_timeout is not None and own_timeout == -1: +- _timeout = int(opt_timeout) ++ if opt_timeout == TIMEOUT_DEFAULT: ++ _timeout = default_timeout ++ else: ++ _timeout = int(opt_timeout) + elif opt_timeout is not None and own_timeout > -1: + _timeout = own_timeout + else: +-- +2.34.1 + diff --git a/SPECS/sos.spec b/SPECS/sos.spec index a0f0749..e7c9ca6 100644 --- a/SPECS/sos.spec +++ b/SPECS/sos.spec @@ -5,7 +5,7 @@ Summary: A set of tools to gather troubleshooting information from a system Name: sos Version: 4.2 -Release: 13%{?dist} +Release: 15%{?dist} Group: Applications/System Source0: https://github.com/sosreport/sos/archive/%{version}/sos-%{version}.tar.gz Source1: sos-audit-%{auditversion}.tgz @@ -42,7 +42,8 @@ Patch18: sos-bz2036697-ocp-backports.patch Patch19: sos-bz2043102-foreman-tasks-msgpack.patch Patch20: sos-bz2041488-virsh-in-foreground.patch Patch21: sos-bz2042966-ovn-proper-package-enablement.patch - +Patch22: sos-bz2054882-plugopt-logging-effective-opts.patch +Patch23: sos-bz2055547-honour-plugins-timeout-hardcoded.patch %description Sos is a set of tools that gathers information about system @@ -74,6 +75,8 @@ support technicians and developers. %patch19 -p1 %patch20 -p1 %patch21 -p1 +%patch22 -p1 +%patch23 -p1 %build %py3_build @@ -140,6 +143,18 @@ of the system. Currently storage and filesystem commands are audited. %ghost /etc/audit/rules.d/40-sos-storage.rules %changelog +* Wed Feb 23 2022 Pavel Moravec = 4.2-15 +- [sosnode] Handle downstream versioning for runtime option + Resolves: bz2036697 +- [options] Fix logging on plugopts in effective sos command + Resolves: bz2054882 +- [report] Honor plugins' hardcoded plugin_timeout + Resolves: bz2055547 +- [policies] Set fallback to None sysroot, don't chroot to '/' + Resolves: bz1873185 +- [ovn_central] Rename container responsable of Red Hat + Resolves: bz2042966 + * Wed Jan 26 2022 Pavel Moravec = 4.2-13 - [virsh] Catch parsing exception Resolves: bz2041488