Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 63 additions & 3 deletions avocado/core/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# This code was inspired in the autotest project,
# client/shared/settings.py
# Author: John Admanski <jadmanski@google.com>
import configparser
import filecmp
import logging
import os
Expand Down Expand Up @@ -68,6 +69,47 @@ def __init__(self, basedir=None, log_packages=None, profiler=None):
"""
self.config = settings.as_dict()

# Retrieve the configured paths for sudo commands and distros from the settings dictionary
sudo_commands_conf = self.config.get("sysinfo.sudo_commands", "")
sudo_distros_conf = self.config.get("sysinfo.sudo_distros", "")

if sudo_commands_conf:
log.info("sudo_commands loaded from config: %s", sudo_commands_conf)
else:
log.debug("sudo_commands config is empty or missing")

if sudo_distros_conf:
log.info("sudo_distros loaded from config: %s", sudo_distros_conf)
else:
log.debug("sudo_distros config is empty or missing")

def _load_sudo_list(raw_value, key):
# pylint: disable=wrong-spelling-in-docstring
"""
If `raw_value` is a path to an INI file, read `[sysinfo] / key`
from it; otherwise, treat `raw_value` itself as a CSV list.
"""
if not raw_value:
return ""
if os.path.isfile(raw_value):
parser = configparser.ConfigParser()
parser.read(raw_value)
return parser.get("sysinfo", key, fallback="")
return raw_value

# Retrieve the actual sudo commands and distros values from the config files,
# falling back to empty string if the keys are missing
sudo_commands_value = _load_sudo_list(sudo_commands_conf, "sudo_commands")
sudo_distros_value = _load_sudo_list(sudo_distros_conf, "sudo_distros")

self.sudo_commands = {
cmd.strip().lower() for cmd in sudo_commands_value.split(",") if cmd.strip()
}

self.sudo_distros = {
dst.strip().lower() for dst in sudo_distros_value.split(",") if dst.strip()
}

if basedir is None:
basedir = utils_path.init_dir("sysinfo")
self.basedir = basedir
Expand Down Expand Up @@ -136,15 +178,33 @@ def _set_collectibles(self):

for cmd in self.sysinfo_files["commands"]:
self.start_collectibles.add(
sysinfo.Command(cmd, timeout=timeout, locale=locale)
sysinfo.Command(
cmd,
timeout=timeout,
locale=locale,
sudo_commands=self.sudo_commands,
sudo_distros=self.sudo_distros,
)
)
self.end_collectibles.add(
sysinfo.Command(cmd, timeout=timeout, locale=locale)
sysinfo.Command(
cmd,
timeout=timeout,
locale=locale,
sudo_commands=self.sudo_commands,
sudo_distros=self.sudo_distros,
)
)

for fail_cmd in self.sysinfo_files["fail_commands"]:
self.end_fail_collectibles.add(
sysinfo.Command(fail_cmd, timeout=timeout, locale=locale)
sysinfo.Command(
fail_cmd,
timeout=timeout,
locale=locale,
sudo_commands=self.sudo_commands,
sudo_distros=self.sudo_distros,
)
)

for filename in self.sysinfo_files["files"]:
Expand Down
6 changes: 6 additions & 0 deletions avocado/etc/avocado/sysinfo.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[sysinfo]
sudo_commands = dmidecode,fdisk
# Add any other commands that require sudo here, separated by commas.
sudo_distros = uos,deepin
# Add any other operating system that require sudo here, separated by commas.
# Values of sudo_distros must match the ID= field from /etc/os-release (e.g. uos, deepin).
22 changes: 22 additions & 0 deletions avocado/plugins/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,28 @@ def initialize(self):
help_msg=help_msg,
)

help_msg = "File with list of commands that require sudo"
default = system_wide_or_base_path("etc/avocado/sysinfo.conf")
settings.register_option(
section="sysinfo",
key="sudo_commands",
key_type=prepend_base_path,
default=default,
help_msg=help_msg,
)

help_msg = (
"File with list of distributions (values matching ID= in /etc/os-release) "
"that require sudo"
)
settings.register_option(
section="sysinfo",
key="sudo_distros",
key_type=prepend_base_path,
default=default,
help_msg=help_msg,
)


class SysInfoJob(JobPreTests, JobPostTests):

Expand Down
77 changes: 76 additions & 1 deletion avocado/utils/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
# John Admanski <jadmanski@google.com>

import json
import logging
import os
import platform
import shlex
import subprocess
import tempfile
from abc import ABC, abstractmethod

from avocado.utils import astring, process
from avocado.utils.process import can_sudo

DATA_SIZE = 200000
log = logging.getLogger("avocado.sysinfo")


class CollectibleException(Exception):
Expand Down Expand Up @@ -132,12 +136,23 @@ class Command(Collectible):
:param locale: Force LANG for sysinfo collection
"""

def __init__(self, cmd, timeout=-1, locale="C"):
def __init__(
self, cmd, timeout=-1, locale="C", sudo_commands=None, sudo_distros=None
): # pylint: disable=R0913
super().__init__(cmd)
self._name = self.log_path
self.cmd = cmd
self.timeout = timeout
self.locale = locale
self.sudo_commands = sudo_commands
self.sudo_distros = sudo_distros
self._sysinfo_cmd = None

@property
def _sudoer(self):
if self._sysinfo_cmd is None and self.sudo_commands and self.sudo_distros:
self._sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
return self._sysinfo_cmd

def __repr__(self):
r = "Command(%r, %r)"
Expand Down Expand Up @@ -168,6 +183,13 @@ def collect(self):
# but the avocado.utils.process APIs define no timeouts as "None"
if int(self.timeout) <= 0:
self.timeout = None

# Determine whether to run with sudo (do not mutate the command string)
sudo_flag = False
if self._sudoer:
sudo_flag = self._sudoer.use_sudo() and self._sudoer.is_sudo_cmd(self.cmd)
log.info("Executing Command%s: %s", " (sudo)" if sudo_flag else "", self.cmd)

try:
result = process.run(
self.cmd,
Expand All @@ -176,6 +198,7 @@ def collect(self):
ignore_status=True,
shell=True,
env=env,
sudo=sudo_flag,
)
yield result.stdout
except FileNotFoundError as exc_fnf:
Expand Down Expand Up @@ -394,3 +417,55 @@ def collect(self):
raise CollectibleException(
f"Not logging {self.path} " f"(lack of permissions)"
) from exc


class SysinfoCommand:
def __init__(self, sudo_commands=None, sudo_distros=None):
self.sudo_cmds = sudo_commands if sudo_commands else set()
self.sudo_distros = sudo_distros if sudo_distros else set()
self.sudo_available = False
# Only attempt sudo capability detection on Linux, where it is relevant.
if platform.system().lower() == "linux":
self.sudo_available = can_sudo()

def use_sudo(self):
"""
Determine if 'sudo' should be used based on the system type.

Returns:
bool: True if 'sudo' should be used, False otherwise.
"""
if not self.sudo_available:
return False
system_name = platform.system().lower()
if system_name == "linux":
if hasattr(os, "geteuid") and not os.geteuid():
return False
try:
with open("/etc/os-release", encoding="utf-8") as f:
for line in f:
if line.startswith("ID="):
os_id = line.strip().split("=")[1].strip('"')
return os_id.lower() in self.sudo_distros
except FileNotFoundError:
log.debug("/etc/os-release not found.")
return False
return False
return False

def is_sudo_cmd(self, cmd):
"""
Determine if 'sudo' should be used for a specific command based on the configuration.

Args:
cmd (str): The command to check.

Returns:
bool: True if 'sudo' should be used, False otherwise.
"""
try:
first = shlex.split(cmd or "")[0]
except (ValueError, IndexError):
return False
base = os.path.basename(first).lower()
return base in self.sudo_cmds
1 change: 1 addition & 0 deletions python-avocado.spec
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ Common files (such as configuration) for the Avocado Testing Framework.
%dir %{_datarootdir}/avocado/schemas
%{_datarootdir}/avocado/schemas/*
%config(noreplace)%{_sysconfdir}/avocado/sysinfo/commands
%config(noreplace)%{_sysconfdir}/avocado/sysinfo.conf
%config(noreplace)%{_sysconfdir}/avocado/sysinfo/files
%config(noreplace)%{_sysconfdir}/avocado/sysinfo/profilers
%config(noreplace)%{_sysconfdir}/avocado/scripts/job/pre.d/README
Expand Down
Loading