1317 lines
54 KiB
Diff
1317 lines
54 KiB
Diff
|
From 5b245b1e449c6a05d09034bcb8290bffded79327 Mon Sep 17 00:00:00 2001
|
||
|
From: Pavel Moravec <pmoravec@redhat.com>
|
||
|
Date: Wed, 8 Sep 2021 17:04:58 +0200
|
||
|
Subject: [PATCH] [report] Implement --estimate-only
|
||
|
|
||
|
Add report option --estimate-only to estimate disk space requirements
|
||
|
when running a sos report.
|
||
|
|
||
|
Resolves: #2673
|
||
|
|
||
|
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
|
||
|
---
|
||
|
man/en/sos-report.1 | 13 +++++++-
|
||
|
sos/report/__init__.py | 74 ++++++++++++++++++++++++++++++++++++++++--
|
||
|
2 files changed, 84 insertions(+), 3 deletions(-)
|
||
|
|
||
|
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
|
||
|
index 36b337df..e8efc8f8 100644
|
||
|
--- a/man/en/sos-report.1
|
||
|
+++ b/man/en/sos-report.1
|
||
|
@@ -14,7 +14,7 @@ sos report \- Collect and package diagnostic and support data
|
||
|
[--preset preset] [--add-preset add_preset]\fR
|
||
|
[--del-preset del_preset] [--desc description]\fR
|
||
|
[--batch] [--build] [--debug] [--dry-run]\fR
|
||
|
- [--label label] [--case-id id]\fR
|
||
|
+ [--estimate-only] [--label label] [--case-id id]\fR
|
||
|
[--threads threads]\fR
|
||
|
[--plugin-timeout TIMEOUT]\fR
|
||
|
[--cmd-timeout TIMEOUT]\fR
|
||
|
@@ -317,6 +317,17 @@ output, or string data from the system. The resulting logs may be used
|
||
|
to understand the actions that sos would have taken without the dry run
|
||
|
option.
|
||
|
.TP
|
||
|
+.B \--estimate-only
|
||
|
+Estimate disk space requirements when running sos report. This can be valuable
|
||
|
+to prevent sosreport working dir to consume all free disk space. No plugin data
|
||
|
+is available at the end.
|
||
|
+
|
||
|
+Plugins will be collected sequentially, size of collected files and commands outputs
|
||
|
+will be calculated and the plugin files will be immediatelly deleted prior execution
|
||
|
+of the next plugin. This still can consume whole free disk space, though. Please note,
|
||
|
+size estimations may not be accurate for highly utilized systems due to changes between
|
||
|
+an estimate and a real execution.
|
||
|
+.TP
|
||
|
.B \--upload
|
||
|
If specified, attempt to upload the resulting archive to a vendor defined location.
|
||
|
|
||
|
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
|
||
|
index 82484f1d..b033f621 100644
|
||
|
--- a/sos/report/__init__.py
|
||
|
+++ b/sos/report/__init__.py
|
||
|
@@ -86,6 +86,7 @@ class SoSReport(SoSComponent):
|
||
|
'desc': '',
|
||
|
'domains': [],
|
||
|
'dry_run': False,
|
||
|
+ 'estimate_only': False,
|
||
|
'experimental': False,
|
||
|
'enable_plugins': [],
|
||
|
'keywords': [],
|
||
|
@@ -137,6 +138,7 @@ class SoSReport(SoSComponent):
|
||
|
self._args = args
|
||
|
self.sysroot = "/"
|
||
|
self.preset = None
|
||
|
+ self.estimated_plugsizes = {}
|
||
|
|
||
|
self.print_header()
|
||
|
self._set_debug()
|
||
|
@@ -223,6 +225,11 @@ class SoSReport(SoSComponent):
|
||
|
help="Description for a new preset",)
|
||
|
report_grp.add_argument("--dry-run", action="store_true",
|
||
|
help="Run plugins but do not collect data")
|
||
|
+ report_grp.add_argument("--estimate-only", action="store_true",
|
||
|
+ help="Approximate disk space requirements for "
|
||
|
+ "a real sos run; disables --clean and "
|
||
|
+ "--collect, sets --threads=1 and "
|
||
|
+ "--no-postproc")
|
||
|
report_grp.add_argument("--experimental", action="store_true",
|
||
|
dest="experimental", default=False,
|
||
|
help="enable experimental plugins")
|
||
|
@@ -700,6 +700,33 @@ class SoSReport(SoSComponent):
|
||
|
self.all_options.append((plugin, plugin_name, optname,
|
||
|
optparm))
|
||
|
|
||
|
+ def _set_estimate_only(self):
|
||
|
+ # set estimate-only mode by enforcing some options settings
|
||
|
+ # and return a corresponding log messages string
|
||
|
+ msg = "\nEstimate-only mode enabled"
|
||
|
+ ext_msg = []
|
||
|
+ if self.opts.threads > 1:
|
||
|
+ ext_msg += ["--threads=%s overriden to 1" % self.opts.threads, ]
|
||
|
+ self.opts.threads = 1
|
||
|
+ if not self.opts.build:
|
||
|
+ ext_msg += ["--build enabled", ]
|
||
|
+ self.opts.build = True
|
||
|
+ if not self.opts.no_postproc:
|
||
|
+ ext_msg += ["--no-postproc enabled", ]
|
||
|
+ self.opts.no_postproc = True
|
||
|
+ if self.opts.clean:
|
||
|
+ ext_msg += ["--clean disabled", ]
|
||
|
+ self.opts.clean = False
|
||
|
+ if self.opts.upload:
|
||
|
+ ext_msg += ["--upload* options disabled", ]
|
||
|
+ self.opts.upload = False
|
||
|
+ if ext_msg:
|
||
|
+ msg += ", which overrides some options:\n " + "\n ".join(ext_msg)
|
||
|
+ else:
|
||
|
+ msg += "."
|
||
|
+ msg += "\n\n"
|
||
|
+ return msg
|
||
|
+
|
||
|
def _report_profiles_and_plugins(self):
|
||
|
self.ui_log.info("")
|
||
|
if len(self.loaded_plugins):
|
||
|
@@ -875,10 +909,12 @@ class SoSReport(SoSComponent):
|
||
|
return True
|
||
|
|
||
|
def batch(self):
|
||
|
+ msg = self.policy.get_msg()
|
||
|
+ if self.opts.estimate_only:
|
||
|
+ msg += self._set_estimate_only()
|
||
|
if self.opts.batch:
|
||
|
- self.ui_log.info(self.policy.get_msg())
|
||
|
+ self.ui_log.info(msg)
|
||
|
else:
|
||
|
- msg = self.policy.get_msg()
|
||
|
msg += _("Press ENTER to continue, or CTRL-C to quit.\n")
|
||
|
try:
|
||
|
input(msg)
|
||
|
@@ -1011,6 +1047,22 @@ class SoSReport(SoSComponent):
|
||
|
self.running_plugs.remove(plugin[1])
|
||
|
self.loaded_plugins[plugin[0]-1][1].set_timeout_hit()
|
||
|
pool._threads.clear()
|
||
|
+ if self.opts.estimate_only:
|
||
|
+ from pathlib import Path
|
||
|
+ tmpdir_path = Path(self.archive.get_tmp_dir())
|
||
|
+ self.estimated_plugsizes[plugin[1]] = sum(
|
||
|
+ [f.stat().st_size for f in tmpdir_path.glob('**/*')
|
||
|
+ if (os.path.isfile(f) and not os.path.islink(f))])
|
||
|
+ # remove whole tmp_dir content - including "sos_commands" and
|
||
|
+ # similar dirs that will be re-created on demand by next plugin
|
||
|
+ # if needed; it is less error-prone approach than skipping
|
||
|
+ # deletion of some dirs but deleting their content
|
||
|
+ for f in os.listdir(self.archive.get_tmp_dir()):
|
||
|
+ f = os.path.join(self.archive.get_tmp_dir(), f)
|
||
|
+ if os.path.isdir(f):
|
||
|
+ rmtree(f)
|
||
|
+ else:
|
||
|
+ os.unlink(f)
|
||
|
return True
|
||
|
|
||
|
def collect_plugin(self, plugin):
|
||
|
@@ -1330,6 +1382,24 @@ class SoSReport(SoSComponent):
|
||
|
self.policy.display_results(archive, directory, checksum,
|
||
|
map_file=map_file)
|
||
|
|
||
|
+ if self.opts.estimate_only:
|
||
|
+ from sos.utilities import get_human_readable
|
||
|
+ _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
|
||
|
+ self.ui_log.info("Estimated disk space requirement for whole "
|
||
|
+ "uncompressed sos report directory: %s" % _sum)
|
||
|
+ bigplugins = sorted(self.estimated_plugsizes.items(),
|
||
|
+ key=lambda x: x[1], reverse=True)[:3]
|
||
|
+ bp_out = ", ".join("%s: %s" %
|
||
|
+ (p, get_human_readable(v, precision=0))
|
||
|
+ for p, v in bigplugins)
|
||
|
+ self.ui_log.info("Three biggest plugins: %s" % bp_out)
|
||
|
+ self.ui_log.info("")
|
||
|
+ self.ui_log.info("Please note the estimation is relevant to the "
|
||
|
+ "current options.")
|
||
|
+ self.ui_log.info("Be aware that the real disk space requirements "
|
||
|
+ "might be different.")
|
||
|
+ self.ui_log.info("")
|
||
|
+
|
||
|
if self.opts.upload or self.opts.upload_url:
|
||
|
if not self.opts.build:
|
||
|
try:
|
||
|
--
|
||
|
2.31.1
|
||
|
|
||
|
From 7ae47e6c0717c0b56c3368008dd99a87f7f436d5 Mon Sep 17 00:00:00 2001
|
||
|
From: Pavel Moravec <pmoravec@redhat.com>
|
||
|
Date: Wed, 13 Oct 2021 20:21:16 +0200
|
||
|
Subject: [PATCH] [report] Count with sos_logs and sos_reports in
|
||
|
--estimate-only
|
||
|
|
||
|
Currently, we estimate just plugins' disk space and ignore sos_logs
|
||
|
or sos_reports directories - although they can occupy nontrivial disk
|
||
|
space as well.
|
||
|
|
||
|
Resolves: #2723
|
||
|
|
||
|
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
|
||
|
---
|
||
|
sos/report/__init__.py | 8 ++++++++
|
||
|
1 file changed, 8 insertions(+)
|
||
|
|
||
|
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
|
||
|
index e35c7e8d..7feb31ee 100644
|
||
|
--- a/sos/report/__init__.py
|
||
|
+++ b/sos/report/__init__.py
|
||
|
@@ -1380,6 +1380,14 @@ class SoSReport(SoSComponent):
|
||
|
|
||
|
if self.opts.estimate_only:
|
||
|
from sos.utilities import get_human_readable
|
||
|
+ from pathlib import Path
|
||
|
+ # add sos_logs, sos_reports dirs, etc., basically everything
|
||
|
+ # that remained in self.tmpdir after plugins' contents removal
|
||
|
+ # that still will be moved to the sos report final directory path
|
||
|
+ tmpdir_path = Path(self.tmpdir)
|
||
|
+ self.estimated_plugsizes['sos_logs_reports'] = sum(
|
||
|
+ [f.stat().st_size for f in tmpdir_path.glob('**/*')])
|
||
|
+
|
||
|
_sum = get_human_readable(sum(self.estimated_plugsizes.values()))
|
||
|
self.ui_log.info("Estimated disk space requirement for whole "
|
||
|
"uncompressed sos report directory: %s" % _sum)
|
||
|
--
|
||
|
2.31.1
|
||
|
|
||
|
From 4293f3317505661e8f32ba94ad87310996fa1626 Mon Sep 17 00:00:00 2001
|
||
|
From: Eric Desrochers <eric.desrochers@canonical.com>
|
||
|
Date: Tue, 19 Oct 2021 12:18:40 -0400
|
||
|
Subject: [PATCH] [report] check for symlink before rmtree when opt
|
||
|
estimate-only is use
|
||
|
|
||
|
Check if the dir is also symlink before performing rmtree()
|
||
|
method so that unlink() method can be used instead.
|
||
|
|
||
|
Traceback (most recent call last):
|
||
|
File "./bin/sos", line 22, in <module>
|
||
|
sos.execute()
|
||
|
File "/tmp/sos/sos/__init__.py", line 186, in execute
|
||
|
self._component.execute()
|
||
|
OSError: Cannot call rmtree on a symbolic link
|
||
|
|
||
|
Closes: #2727
|
||
|
|
||
|
Signed-off-by: Eric Desrochers <eric.desrochers@canonical.com>
|
||
|
---
|
||
|
sos/report/__init__.py | 2 +-
|
||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||
|
|
||
|
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
|
||
|
index 7feb31ee..1b5bc97d 100644
|
||
|
--- a/sos/report/__init__.py
|
||
|
+++ b/sos/report/__init__.py
|
||
|
@@ -1059,7 +1059,7 @@ class SoSReport(SoSComponent):
|
||
|
# deletion of some dirs but deleting their content
|
||
|
for f in os.listdir(self.archive.get_tmp_dir()):
|
||
|
f = os.path.join(self.archive.get_tmp_dir(), f)
|
||
|
- if os.path.isdir(f):
|
||
|
+ if os.path.isdir(f) and not os.path.islink(f):
|
||
|
rmtree(f)
|
||
|
else:
|
||
|
os.unlink(f)
|
||
|
--
|
||
|
2.31.1
|
||
|
|
||
|
From 589d47c93257b55bc796ef6ac25b88c974ee3d72 Mon Sep 17 00:00:00 2001
|
||
|
From: Pavel Moravec <pmoravec@redhat.com>
|
||
|
Date: Mon, 8 Nov 2021 16:38:24 +0100
|
||
|
Subject: [PATCH] [report] Calculate sizes of dirs, symlinks and manifest in
|
||
|
estimate mode
|
||
|
|
||
|
Enhance --estimate-mode to calculate sizes of also:
|
||
|
- symlinks
|
||
|
- directories themselves
|
||
|
- manifest.json file
|
||
|
|
||
|
Use os.lstat() method instead of os.stat() to properly calculate the
|
||
|
sizes (and not destinations of symlinks, e.g.).
|
||
|
|
||
|
Print five biggest plugins instead of three as sos logs and reports do
|
||
|
stand as one "plugin" in the list, often.
|
||
|
|
||
|
Resolves: #2752
|
||
|
|
||
|
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
|
||
|
---
|
||
|
sos/report/__init__.py | 56 +++++++++++++++++++++---------------------
|
||
|
1 file changed, 28 insertions(+), 28 deletions(-)
|
||
|
|
||
|
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
|
||
|
index 10952566..a4c92acc 100644
|
||
|
--- a/sos/report/__init__.py
|
||
|
+++ b/sos/report/__init__.py
|
||
|
@@ -1050,8 +1050,7 @@ class SoSReport(SoSComponent):
|
||
|
from pathlib import Path
|
||
|
tmpdir_path = Path(self.archive.get_tmp_dir())
|
||
|
self.estimated_plugsizes[plugin[1]] = sum(
|
||
|
- [f.stat().st_size for f in tmpdir_path.glob('**/*')
|
||
|
- if (os.path.isfile(f) and not os.path.islink(f))])
|
||
|
+ [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
|
||
|
# remove whole tmp_dir content - including "sos_commands" and
|
||
|
# similar dirs that will be re-created on demand by next plugin
|
||
|
# if needed; it is less error-prone approach than skipping
|
||
|
@@ -1273,6 +1272,33 @@ class SoSReport(SoSComponent):
|
||
|
short_name='manifest.json'
|
||
|
)
|
||
|
|
||
|
+ # print results in estimate mode (to include also just added manifest)
|
||
|
+ if self.opts.estimate_only:
|
||
|
+ from sos.utilities import get_human_readable
|
||
|
+ from pathlib import Path
|
||
|
+ # add sos_logs, sos_reports dirs, etc., basically everything
|
||
|
+ # that remained in self.tmpdir after plugins' contents removal
|
||
|
+ # that still will be moved to the sos report final directory path
|
||
|
+ tmpdir_path = Path(self.tmpdir)
|
||
|
+ self.estimated_plugsizes['sos_logs_reports'] = sum(
|
||
|
+ [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
|
||
|
+
|
||
|
+ _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
|
||
|
+ self.ui_log.info("Estimated disk space requirement for whole "
|
||
|
+ "uncompressed sos report directory: %s" % _sum)
|
||
|
+ bigplugins = sorted(self.estimated_plugsizes.items(),
|
||
|
+ key=lambda x: x[1], reverse=True)[:5]
|
||
|
+ bp_out = ", ".join("%s: %s" %
|
||
|
+ (p, get_human_readable(v, precision=0))
|
||
|
+ for p, v in bigplugins)
|
||
|
+ self.ui_log.info("Five biggest plugins: %s" % bp_out)
|
||
|
+ self.ui_log.info("")
|
||
|
+ self.ui_log.info("Please note the estimation is relevant to the "
|
||
|
+ "current options.")
|
||
|
+ self.ui_log.info("Be aware that the real disk space requirements "
|
||
|
+ "might be different.")
|
||
|
+ self.ui_log.info("")
|
||
|
+
|
||
|
# package up and compress the results
|
||
|
if not self.opts.build:
|
||
|
old_umask = os.umask(0o077)
|
||
|
@@ -1377,32 +1403,6 @@ class SoSReport(SoSComponent):
|
||
|
self.policy.display_results(archive, directory, checksum,
|
||
|
map_file=map_file)
|
||
|
|
||
|
- if self.opts.estimate_only:
|
||
|
- from sos.utilities import get_human_readable
|
||
|
- from pathlib import Path
|
||
|
- # add sos_logs, sos_reports dirs, etc., basically everything
|
||
|
- # that remained in self.tmpdir after plugins' contents removal
|
||
|
- # that still will be moved to the sos report final directory path
|
||
|
- tmpdir_path = Path(self.tmpdir)
|
||
|
- self.estimated_plugsizes['sos_logs_reports'] = sum(
|
||
|
- [f.stat().st_size for f in tmpdir_path.glob('**/*')])
|
||
|
-
|
||
|
- _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
|
||
|
- self.ui_log.info("Estimated disk space requirement for whole "
|
||
|
- "uncompressed sos report directory: %s" % _sum)
|
||
|
- bigplugins = sorted(self.estimated_plugsizes.items(),
|
||
|
- key=lambda x: x[1], reverse=True)[:3]
|
||
|
- bp_out = ", ".join("%s: %s" %
|
||
|
- (p, get_human_readable(v, precision=0))
|
||
|
- for p, v in bigplugins)
|
||
|
- self.ui_log.info("Three biggest plugins: %s" % bp_out)
|
||
|
- self.ui_log.info("")
|
||
|
- self.ui_log.info("Please note the estimation is relevant to the "
|
||
|
- "current options.")
|
||
|
- self.ui_log.info("Be aware that the real disk space requirements "
|
||
|
- "might be different.")
|
||
|
- self.ui_log.info("")
|
||
|
-
|
||
|
if self.opts.upload or self.opts.upload_url:
|
||
|
if not self.opts.build:
|
||
|
try:
|
||
|
--
|
||
|
2.31.1
|
||
|
|
||
|
From c6a5bbb8d75aadd5c7f76d3f469929aba2cf8060 Mon Sep 17 00:00:00 2001
|
||
|
From: Pavel Moravec <pmoravec@redhat.com>
|
||
|
Date: Wed, 5 Jan 2022 10:33:58 +0100
|
||
|
Subject: [PATCH] [report] Provide better warning about estimate-mode
|
||
|
|
||
|
As --estimate-only calculates disk usage based on `stat` data that
|
||
|
differs from outputs of other commands like `du`, enhance the warning
|
||
|
about reliability of the calculated estimation.
|
||
|
|
||
|
Also add a rule-of-thumb recommendation of real disk space requirements.
|
||
|
|
||
|
Resolves: #2815
|
||
|
|
||
|
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
|
||
|
---
|
||
|
man/en/sos-report.1 | 10 +++++++---
|
||
|
sos/report/__init__.py | 3 ++-
|
||
|
2 files changed, 9 insertions(+), 4 deletions(-)
|
||
|
|
||
|
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
|
||
|
index 464a77e54..e34773986 100644
|
||
|
--- a/man/en/sos-report.1
|
||
|
+++ b/man/en/sos-report.1
|
||
|
@@ -343,9 +343,13 @@ is available at the end.
|
||
|
|
||
|
Plugins will be collected sequentially, size of collected files and commands outputs
|
||
|
will be calculated and the plugin files will be immediatelly deleted prior execution
|
||
|
-of the next plugin. This still can consume whole free disk space, though. Please note,
|
||
|
-size estimations may not be accurate for highly utilized systems due to changes between
|
||
|
-an estimate and a real execution.
|
||
|
+of the next plugin. This still can consume whole free disk space, though.
|
||
|
+
|
||
|
+Please note, size estimations may not be accurate for highly utilized systems due to
|
||
|
+changes between an estimate and a real execution. Also some difference between
|
||
|
+estimation (using `stat` command) and other commands used (i.e. `du`).
|
||
|
+
|
||
|
+A rule of thumb is to reserve at least double the estimation.
|
||
|
.TP
|
||
|
.B \--upload
|
||
|
If specified, attempt to upload the resulting archive to a vendor defined location.
|
||
|
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
|
||
|
index ef61fb344..e0617b45e 100644
|
||
|
--- a/sos/report/__init__.py
|
||
|
+++ b/sos/report/__init__.py
|
||
|
@@ -1330,7 +1330,8 @@ def final_work(self):
|
||
|
self.ui_log.info("Please note the estimation is relevant to the "
|
||
|
"current options.")
|
||
|
self.ui_log.info("Be aware that the real disk space requirements "
|
||
|
- "might be different.")
|
||
|
+ "might be different. A rule of thumb is to "
|
||
|
+ "reserve at least double the estimation.")
|
||
|
self.ui_log.info("")
|
||
|
|
||
|
# package up and compress the results
|
||
|
From f22efe044f1f0565b57d6aeca2081a5227e0312c Mon Sep 17 00:00:00 2001
|
||
|
From: Jake Hunsaker <jhunsake@redhat.com>
|
||
|
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 <jhunsake@redhat.com>
|
||
|
---
|
||
|
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 <jhunsake@redhat.com>
|
||
|
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 <jhunsake@redhat.com>
|
||
|
---
|
||
|
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 <jhunsake@redhat.com>
|
||
|
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 <jhunsake@redhat.com>
|
||
|
---
|
||
|
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 <jhunsake@redhat.com>
|
||
|
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 <jhunsake@redhat.com>
|
||
|
---
|
||
|
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 <pmoravec@redhat.com>
|
||
|
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 <pmoravec@redhat.com>
|
||
|
---
|
||
|
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
|
||
|
|