aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Hunsaker <jhunsake@redhat.com>2021-05-18 14:10:41 -0400
committerJake Hunsaker <jhunsake@redhat.com>2021-06-30 16:34:26 -0400
commit0c92c0f0314bdefef599c55ccf28877a8c8215f5 (patch)
tree77368fb6ad7fc389df68a8acfdc3ba4e1af6aa4b
parent757c9e4b6b5572e58f05aaa0952bcfef5b4e49bd (diff)
downloadsos-0c92c0f0314bdefef599c55ccf28877a8c8215f5.tar.gz
[utilities,Plugin] Add sysroot wrappers for common os.path methods
Adds wrapper methods to `sos.utilities` that allows the common `os` methods, such as `os.path.exists()` to account for the setting of `sysroot`. This will allow sos, and particularly plugins, to more easily and reliably function as expected when sos is run within a container. Helpers have been added directly to `Plugin` that automatically pass the set `sysroot` option, so plugin authors do not need to handle that option or directly import these path functions from `sos.utilities`. Closes: #494 Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
-rw-r--r--sos/report/plugins/__init__.py90
-rw-r--r--sos/utilities.py27
-rw-r--r--tests/unittests/plugin_tests.py1
3 files changed, 106 insertions, 12 deletions
diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py
index b9cd28ed..8c9e5f3a 100644
--- a/sos/report/plugins/__init__.py
+++ b/sos/report/plugins/__init__.py
@@ -11,7 +11,8 @@
""" This exports methods available for use by plugins for sos """
from sos.utilities import (sos_get_command_output, import_module, grep,
- fileobj, tail, is_executable)
+ fileobj, tail, is_executable, path_exists,
+ path_isdir, path_isfile, path_islink, listdir)
import os
import glob
import re
@@ -1035,13 +1036,13 @@ class Plugin(object):
reldest = linkdest
self._log_debug("copying link '%s' pointing to '%s' with isdir=%s"
- % (srcpath, linkdest, os.path.isdir(absdest)))
+ % (srcpath, linkdest, self.path_isdir(absdest)))
dstpath = self.strip_sysroot(srcpath)
# use the relative target path in the tarball
self.archive.add_link(reldest, dstpath)
- if os.path.isdir(absdest):
+ if self.path_isdir(absdest):
self._log_debug("link '%s' is a directory, skipping..." % linkdest)
return
@@ -1074,7 +1075,7 @@ class Plugin(object):
def _copy_dir(self, srcpath):
try:
- for name in os.listdir(srcpath):
+ for name in self.listdir(srcpath):
self._log_debug("recursively adding '%s' from '%s'"
% (name, srcpath))
path = os.path.join(srcpath, name)
@@ -1157,7 +1158,7 @@ class Plugin(object):
else:
if stat.S_ISDIR(st.st_mode) and os.access(srcpath, os.R_OK):
# copy empty directory
- if not os.listdir(srcpath):
+ if not self.listdir(srcpath):
self.archive.add_dir(dest)
return
self._copy_dir(srcpath)
@@ -1483,7 +1484,7 @@ class Plugin(object):
except OSError:
# if _file is a broken symlink, we should collect it,
# otherwise skip it
- if os.path.islink(_file):
+ if self.path_islink(_file):
file_size = 0
else:
self._log_info("failed to stat '%s', skipping" % _file)
@@ -2507,9 +2508,9 @@ class Plugin(object):
for path in paths:
try:
# avoid recursive symlink dirs
- if os.path.isfile(path) or os.path.islink(path):
+ if self.path_isfile(path) or self.path_islink(path):
found_paths.append(path)
- elif os.path.isdir(path) and os.listdir(path):
+ elif self.path_isdir(path) and self.listdir(path):
found_paths.extend(__expand(os.path.join(path, '*')))
else:
found_paths.append(path)
@@ -2523,15 +2524,15 @@ class Plugin(object):
pass
return list(set(found_paths))
- if (os.access(copyspec, os.R_OK) and os.path.isdir(copyspec) and
- os.listdir(copyspec)):
+ 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, '*')
expanded = glob.glob(copyspec, recursive=True)
recursed_files = []
for _path in expanded:
try:
- if os.path.isdir(_path) and os.listdir(_path):
+ if self.path_isdir(_path) and self.listdir(_path):
# remove the top level dir to avoid duplicate attempts to
# copy the dir and its contents
expanded.remove(_path)
@@ -2669,7 +2670,7 @@ class Plugin(object):
# no checks beyond architecture restrictions
return self.check_is_architecture()
- return ((any(os.path.exists(fname) for fname in files) or
+ 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(self.is_module_loaded(mod) for mod in self.kernel_mods) or
@@ -2728,6 +2729,71 @@ class Plugin(object):
if verify_cmd:
self.add_cmd_output(verify_cmd)
+ def path_exists(self, path):
+ """Helper to call the sos.utilities wrapper that allows the
+ corresponding `os` call to account for sysroot
+
+ :param path: The canonical path for a specific file/directory
+ :type path: ``str``
+
+
+ :returns: True if the path exists in sysroot, else False
+ :rtype: ``bool``
+ """
+ return path_exists(path, self.commons['cmdlineopts'].sysroot)
+
+ def path_isdir(self, path):
+ """Helper to call the sos.utilities wrapper that allows the
+ corresponding `os` call to account for sysroot
+
+ :param path: The canonical path for a specific file/directory
+ :type path: ``str``
+
+
+ :returns: True if the path is a dir, else False
+ :rtype: ``bool``
+ """
+ return path_isdir(path, self.commons['cmdlineopts'].sysroot)
+
+ def path_isfile(self, path):
+ """Helper to call the sos.utilities wrapper that allows the
+ corresponding `os` call to account for sysroot
+
+ :param path: The canonical path for a specific file/directory
+ :type path: ``str``
+
+
+ :returns: True if the path is a file, else False
+ :rtype: ``bool``
+ """
+ return path_isfile(path, self.commons['cmdlineopts'].sysroot)
+
+ def path_islink(self, path):
+ """Helper to call the sos.utilities wrapper that allows the
+ corresponding `os` call to account for sysroot
+
+ :param path: The canonical path for a specific file/directory
+ :type path: ``str``
+
+
+ :returns: True if the path is a link, else False
+ :rtype: ``bool``
+ """
+ return path_islink(path, self.commons['cmdlineopts'].sysroot)
+
+ def listdir(self, path):
+ """Helper to call the sos.utilities wrapper that allows the
+ corresponding `os` call to account for sysroot
+
+ :param path: The canonical path for a specific file/directory
+ :type path: ``str``
+
+
+ :returns: Contents of path, if it is a directory
+ :rtype: ``list``
+ """
+ return listdir(path, self.commons['cmdlineopts'].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 fb13979a..95df19cb 100644
--- a/sos/utilities.py
+++ b/sos/utilities.py
@@ -213,6 +213,33 @@ def get_human_readable(size, precision=2):
return "%.*f%s" % (precision, size, suffixes[suffixindex])
+def _os_wrapper(path, sysroot, method, module=os.path):
+ if sysroot not in [None, '/']:
+ path = os.path.join(sysroot, path.lstrip('/'))
+ _meth = getattr(module, method)
+ return _meth(path)
+
+
+def path_exists(path, sysroot):
+ return _os_wrapper(path, sysroot, 'exists')
+
+
+def path_isdir(path, sysroot):
+ return _os_wrapper(path, sysroot, 'isdir')
+
+
+def path_isfile(path, sysroot):
+ return _os_wrapper(path, sysroot, 'isfile')
+
+
+def path_islink(path, sysroot):
+ return _os_wrapper(path, sysroot, 'islink')
+
+
+def listdir(path, sysroot):
+ return _os_wrapper(path, sysroot, 'listdir', os)
+
+
class AsyncReader(threading.Thread):
"""Used to limit command output to a given size without deadlocking
sos.
diff --git a/tests/unittests/plugin_tests.py b/tests/unittests/plugin_tests.py
index dda8c13a..1d6ce73a 100644
--- a/tests/unittests/plugin_tests.py
+++ b/tests/unittests/plugin_tests.py
@@ -117,6 +117,7 @@ class MockOptions(object):
no_postproc = False
skip_files = []
skip_commands = []
+ sysroot = None
class PluginToolTests(unittest.TestCase):