Skip to content

Conversation

@xianglongfei-8888
Copy link

@xianglongfei-8888 xianglongfei-8888 commented Nov 25, 2025

1.Compatible with UOS/Deepin operating system, the system can also be extended
2.Executing the sudo command is configurable and does not affect other systems, such as "sudo-commands=dmidecode, fdisk"
image

image

Summary by CodeRabbit

  • New Features
    • Added support for running system information collection commands with elevated privileges based on configuration.
    • New configuration options to specify which commands require elevated privileges and which Linux distributions require them.
    • Added a customizable configuration file to define elevated privilege requirements for sysinfo collection.

✏️ Tip: You can customize this high-level summary in your review settings.

@mr-avocado mr-avocado bot moved this to Review Requested in Default project Nov 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

This pull request adds conditional sudo privilege support to Avocado's sysinfo command collection. The implementation introduces a SysinfoCommand helper class that detects Linux sudo capability via can_sudo() and validates whether commands and Linux distributions match configured lists. The Command class is extended to accept sudo_commands and sudo_distros parameters, and Command.collect is updated to conditionally apply a sudo flag to process.run() without mutating the command string. Configuration options are registered in the settings layer and loaded from sysinfo.conf into the SysInfo class, which then propagates these settings to all Command instances. A template configuration file is provided with example commands and distributions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • SysinfoCommand logic: The sudo detection flow via can_sudo() and the distribution matching against /etc/os-release require careful verification
  • Multi-layer configuration propagation: Settings registration → SysInfo loading → Command initialization creates multiple touch points where configuration must flow correctly
  • Command.collect sudo flag application: Verify that sudo flag is passed correctly to process.run() and that command string remains unmodified
  • Distribution and command list normalization: Ensure lowercasing and stripping logic is consistently applied across all layers
  • Edge cases: Non-root users, missing /etc/os-release, empty sudo lists, and unsupported platforms (non-Linux) should be handled gracefully

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.47% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: adding configurable sudo commands and support for UOS/Deepin distributions, which align with the core objectives and file modifications.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb9c412 and b11d1aa.

📒 Files selected for processing (5)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
  • python-avocado.spec (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • avocado/plugins/sysinfo.py
  • avocado/etc/avocado/sysinfo.conf
🧰 Additional context used
🧬 Code graph analysis (1)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: Egg task fedora:40
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Egg task fedora:41
  • GitHub Check: Version task fedora:41
  • GitHub Check: Fedora selftests
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Egg task ubuntu:22.04
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Windows with Python 3.9
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (10)
python-avocado.spec (1)

293-293: LGTM: Config file declaration follows existing patterns.

The new sysinfo.conf is correctly declared with %config(noreplace) to preserve user customizations during package upgrades, consistent with other config files in this section.

avocado/core/sysinfo.py (3)

14-14: LGTM: Import added for config parsing.

The configparser import is required for the new _load_sudo_list helper function.


72-111: Duplicate code issue from past review has been resolved.

The sudo configuration loading logic now appears only once, and the implementation correctly handles both file paths (loading from INI [sysinfo] section) and direct CSV values. The set comprehensions properly normalize values to lowercase and filter empty strings.


179-208: LGTM: Consistent sudo configuration propagation to Command instances.

The sudo_commands and sudo_distros parameters are correctly passed to all three collectible sets (start_collectibles, end_collectibles, and end_fail_collectibles), ensuring uniform sudo behavior across command collection phases.

avocado/utils/sysinfo.py (6)

17-29: LGTM: Required imports and logger setup.

The new imports (logging, platform, can_sudo) and module-level logger are properly added to support the sudo-aware functionality.


186-201: LGTM: Sudo applied via process.run parameter without command mutation.

The implementation correctly determines whether to use sudo based on configuration and passes the flag to process.run(). This approach leverages the existing sudo parameter in the process API rather than modifying the command string directly, which is cleaner and more maintainable.


422-429: Merge conflict from past review has been resolved.

The SysinfoCommand class now correctly accepts injected sudo_commands and sudo_distros parameters without parsing configuration internally, addressing the previous review feedback.


431-454: LGTM: Distro-based sudo eligibility check.

The use_sudo() method correctly:

  1. Checks sudo availability
  2. Skips if already running as root
  3. Reads /etc/os-release to determine the OS ID
  4. Handles missing file gracefully

This enables selective sudo usage for specific distributions (UOS/Deepin as per PR objectives).


456-471: LGTM: Robust command-based sudo check.

The is_sudo_cmd() method properly:

  1. Uses shlex.split for correct command parsing
  2. Handles edge cases (empty, malformed commands)
  3. Extracts the executable basename regardless of path (e.g., /usr/sbin/fdiskfdisk)
  4. Performs case-insensitive comparison via lowercasing

151-155: Both sudo_commands and sudo_distros must be configured for the sudo feature to activate.

The condition on line 153 uses and, meaning if a user provides only one of these parameters, the feature silently fails to activate. If both are required by design, this joint requirement should be documented. If partial configurations are valid, the condition should be adjusted to handle them appropriately.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
avocado/utils/sysinfo.py (4)

175-189: Consider caching SysinfoCommand instance to avoid repeated config parsing.

A new SysinfoCommand() instance is created on every collect() call, which reads and parses the configuration file each time. For batch sysinfo collection with many commands, this adds unnecessary I/O overhead.

Consider making SysinfoCommand a module-level singleton or caching the instance:

+_sysinfo_command = None
+
+def _get_sysinfo_command():
+    global _sysinfo_command
+    if _sysinfo_command is None:
+        _sysinfo_command = SysinfoCommand()
+    return _sysinfo_command

Then in collect():

-        sysinfo_cmd = SysinfoCommand()
+        sysinfo_cmd = _get_sysinfo_command()

410-413: Local import is acceptable but add a brief comment.

The local import of can_sudo inside __init__ works to avoid potential circular imports. A brief comment explaining why would help future readers.


435-440: Root check is redundant.

The can_sudo() function already returns True when running as root (see process.py line 71-72: if not os.getuid(): return True). The explicit geteuid() == 0 check on lines 439-440 is therefore redundant since self.sudo_available will already be True for root, and process.run(..., sudo=True) handles this correctly.

If the intent is to skip the unnecessary sudo prefix for root users, this is fine—but note that process.run() also handles this case internally.


441-446: Specify encoding and consider checking ID_LIKE for derivative distributions.

  1. The file should be opened with an explicit encoding to avoid platform-dependent behavior.
  2. Many distributions set ID_LIKE to indicate their parent (e.g., a UOS derivative might have ID_LIKE=debian). Checking only ID= may miss compatible derivatives.
             try:
-                with open('/etc/os-release') as f:
+                with open('/etc/os-release', encoding='utf-8') as f:
+                    os_id = None
+                    id_like = []
                     for line in f:
                         if line.startswith('ID='):
-                            os_id = line.strip().split('=')[1].strip('"')
-                            return os_id.lower() in self.sudo_distros
+                            os_id = line.strip().split('=')[1].strip('"').lower()
+                        elif line.startswith('ID_LIKE='):
+                            id_like = line.strip().split('=')[1].strip('"').lower().split()
+                    if os_id and os_id in self.sudo_distros:
+                        return True
+                    return any(d in self.sudo_distros for d in id_like)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74b7379 and 9759bba.

📒 Files selected for processing (2)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/utils/sysinfo.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
🔇 Additional comments (3)
avocado/etc/avocado/sysinfo.conf (1)

1-5: Configuration looks reasonable.

The structure is clear and extensible. The configured commands (dmidecode, fdisk) are appropriate choices as they typically require elevated privileges to provide complete information.

Consider adding a comment clarifying that only the base command name (without arguments) should be listed, since is_sudo_cmd() extracts only the basename for matching.

avocado/utils/sysinfo.py (2)

463-468: LGTM - command parsing is robust.

Good use of shlex.split() with proper exception handling for malformed commands, and os.path.basename() ensures paths like /usr/sbin/dmidecode match correctly against the configured base names.


414-421: No changes required — relative path resolves correctly when installed.

The relative path assumption is valid. MANIFEST.in includes recursive-include avocado/etc *, and setup.py has include_package_data=True, which ensures the etc directory structure is preserved in the installed package. The path os.path.dirname(__file__) + ".." + "etc/avocado/sysinfo.conf" from avocado/utils/sysinfo.py correctly resolves to the packaged config file both in development and post-installation.

@xianglongfei-8888
Copy link
Author

@clebergnu Thanks! Merged into one submission, resubmitted PR.
image

@xianglongfei-8888 xianglongfei-8888 changed the title Fixed the issue in sysinfo where the dmidecode and fdisk - l commands… feat: Add configurable sudo commands and UOS/Deepin support Nov 25, 2025
@clebergnu clebergnu self-requested a review November 25, 2025 15:09
Copy link
Contributor

@clebergnu clebergnu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @xianglongfei-8888,

This proposal of selectively using sudo makes sense to me. But, I have some comments about the implementation:

  1. The SysinfoCommand class redoes the parsing of Avocado's configuration, and it does so in a way that is not exactly the same. It's better to use avocado.core.settings.settings instead.
  2. A lot of this code does not fall into the "utility" category. Think of it as if that module was standalone for other people to build upon their final functionality. And with the migration of all avocado.utils.* to the AAUtils project, this will need to be taken into account.

So, being more specific, instead of taking configuration in avocado.utils.sysinfo from the configuration file directly, most of the code should be in the sysinfo plugin, which has access to the effective settings. From these settings, pass them as (new?) parameters to avocado.utils.sysinfo.

Let me know if this makes sense to you.

Thanks!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
avocado/utils/sysinfo.py (2)

21-29: Logger choice is fine; consider whether INFO is the right level for per-command logs

You’ve added a module-level logger log = logging.getLogger("avocado.sysinfo"). Since avocado/core/sysinfo.py uses the same logger name, both modules now share the same logger, which is consistent.

Later you log each command execution at INFO. That may be desirable for troubleshooting, but it can be a bit noisy if sysinfo runs many commands on every job. If log volume becomes an issue, consider switching the per-command message to DEBUG and leaving higher-level sysinfo lifecycle messages at INFO.


411-458: SysinfoCommand logic is mostly correct; a few edge-case and clarity considerations

The overall logic is good: you gate sudo on three things—sudo availability, OS being Linux, and distro ID being listed—then further restrict it to configured commands by basename.

A few specific points worth tightening or at least documenting:

  1. Empty distro list silently disables sudo:

    • If self.sudo_distros is empty (e.g., misconfigured or config file missing the sudo_distros key), use_sudo() will always return False, even if sudo_commands is populated.
    • That’s acceptable if intentional (sudo only opt‑in on explicit distros), but you might want to log at DEBUG when sudo_distros is empty so it’s obvious why sudo is never used.
  2. Error handling for /etc/os-release:

    • On Linux you attempt to read /etc/os-release and log a warning on FileNotFoundError. That makes sense, but consider also logging a brief debug when the file is present yet no ID= line is found, so users can understand why sudo is disabled.
  3. Repeated I/O / parsing:

    • use_sudo() walks /etc/os-release line by line and returns as soon as it finds ID=.... This is efficient per call, but is still repeated for every command. Caching the discovered os_id at module level or as a static attribute on SysinfoCommand would remove redundant work.
  4. Command name extraction is robust:

    • is_sudo_cmd() uses shlex.split() and safely handles malformed commands with a try/except, then normalizes to a lowercase basename. That’s a solid approach and avoids false positives on arguments.

Overall, the behavior is consistent with the design; the suggestions above are mainly to make the sudo decision easier to debug and to avoid surprises when misconfigured lists are empty.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9759bba and 9461cbc.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • avocado/etc/avocado/sysinfo.conf
🧰 Additional context used
🧬 Code graph analysis (3)
avocado/plugins/sysinfo.py (2)
avocado/core/utils/path.py (2)
  • system_wide_or_base_path (15-47)
  • prepend_base_path (7-12)
avocado/core/settings.py (3)
  • register_option (646-803)
  • section (138-139)
  • key (142-143)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (129-202)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (52)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: Fedora selftests
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Egg task ubuntu:24.04
  • GitHub Check: Egg task ubuntu:22.04
  • GitHub Check: Podman Egg task
  • GitHub Check: Build Package (wheel/tarball) for Python 3.12
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (3)
avocado/utils/sysinfo.py (2)

138-146: Command now carries sudo configuration; object-level design looks clean

Extending Command.__init__ to accept sudo_commands and sudo_distros and storing those as attributes is a reasonable way to keep sudo behavior local to the collectible, without touching callers that don’t care about sudo (they continue to use defaults).

No issues with compatibility here: existing call sites that don’t pass the new arguments will still work because the parameters are appended at the end with default None.


162-181: SysinfoCommand integration is correct, but may be slightly heavy-weight per command

The integration of SysinfoCommand into Command.collect() is logically sound:

  • Environment handling and timeout behavior are preserved.
  • You compute sudo_flag = use_sudo() and is_sudo_cmd(self.cmd) and pass it through to process.run(..., sudo=sudo_flag).
  • The decision is based on both distro and command name, which matches the new configuration model.

One potential improvement:

  • SysinfoCommand.__init__ calls can_sudo() and, via use_sudo(), also reads /etc/os-release on each collect() call. In a typical sysinfo run, this can mean reading the same file and probing sudo many times.

If this becomes a hot path, consider caching the distro and sudo availability at module level (or in a small singleton) instead of recomputing them per command. For example, memoize SysinfoCommand.use_sudo() per process, or maintain a module-level "current distro ID" and "sudo_available" flags.

Behaviorally, though, this code looks correct and backward compatible.

avocado/core/sysinfo.py (1)

161-169: Consider propagating sudo settings to fail_commands as well

self.sudo_commands / self.sudo_distros are only passed into sysinfo.Command for the regular commands collectibles. fail_commands are still instantiated without these parameters, meaning commands in fail_commands will not benefit from the sudo routing logic even if explicitly configured.

Either align the behavior by propagating the same sudo config to fail_commands:

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

Or add a comment explaining why fail_commands intentionally exclude these settings to prevent future confusion.

@codecov
Copy link

codecov bot commented Dec 10, 2025

Codecov Report

❌ Patch coverage is 68.49315% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.46%. Comparing base (74b7379) to head (b11d1aa).
⚠️ Report is 11 commits behind head on master.

Files with missing lines Patch % Lines
avocado/utils/sysinfo.py 59.57% 19 Missing ⚠️
avocado/core/sysinfo.py 80.95% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6241      +/-   ##
==========================================
- Coverage   73.48%   73.46%   -0.03%     
==========================================
  Files         206      206              
  Lines       22494    22571      +77     
==========================================
+ Hits        16530    16582      +52     
- Misses       5964     5989      +25     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
avocado/core/sysinfo.py (1)

72-106: Config options still behave as file paths, so CSV-style sysinfo.sudo_* usage is silently ignored

sysinfo.sudo_commands and sysinfo.sudo_distros are read from settings.as_dict() into sudo_commands_conf / sudo_distros_conf, then always treated as filenames fed into ConfigParser.read(). The actual comma‑separated values come from a [sysinfo] section in those files.

If a user instead follows the “comma separated list” semantics (for example: sysinfo.sudo_commands = dmidecode,fdisk in their config), this string is passed to config.read() as a filename, no file is loaded, the config.get(..., fallback="") calls return empty strings, and both self.sudo_commands and self.sudo_distros end up empty. That effectively disables the sudo logic without any warning, which is the same behavioral problem raised in the previous review.

To support both “option‑as‑value” (CSV) and “option‑as‑path to INI” models, you can fall back to treating the option itself as CSV when it doesn’t point to an existing file, instead of unconditionally relying on ConfigParser.read():

-        config = configparser.ConfigParser()
-        candidates = [sudo_commands_conf, sudo_distros_conf]
-        config.read([p for p in candidates if p])
-
-        # 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 = config.get("sysinfo", "sudo_commands", fallback="")
-        sudo_distros_value = config.get("sysinfo", "sudo_distros", fallback="")
+        config = configparser.ConfigParser()
+
+        def _load_sudo_list(raw_value, key):
+            """
+            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):
+                config.read(raw_value)
+                return config.get("sysinfo", key, fallback="")
+            return raw_value
+
+        sudo_commands_value = _load_sudo_list(sudo_commands_conf, "sudo_commands")
+        sudo_distros_value = _load_sudo_list(sudo_distros_conf, "sudo_distros")

This keeps the current “path to sysinfo.conf” default working, while also making direct CSV configuration on sysinfo.sudo_commands/sysinfo.sudo_distros behave as documented instead of being ignored. You’ll also want to ensure the help text in the plugin matches this dual behavior.

🧹 Nitpick comments (2)
avocado/utils/sysinfo.py (2)

138-147: Avoid paying sudo detection cost when there is no sudo configuration

Command.collect() always instantiates SysinfoCommand:

sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)

SysinfoCommand.__init__ calls can_sudo(), and use_sudo() reads /etc/os-release. This will run for every Command, even when sudo_commands / sudo_distros are empty (the common case on non‑UOS/Deepin systems), yielding sudo_flag == False but still adding non‑trivial overhead.

You can short‑circuit when no sudo configuration is present and only construct SysinfoCommand when it can actually be useful:

-        # Determine whether to run with sudo (do not mutate the command string)
-        sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
-        sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
+        # Determine whether to run with sudo (do not mutate the command string)
+        sudo_flag = False
+        if self.sudo_commands and self.sudo_distros:
+            sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
+            sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)

This keeps behavior unchanged when sudo is configured, and avoids repeated can_sudo() and distro detection calls when it is not.

Also applies to: 179-183, 192-192


413-460: Clarify intended semantics for empty sudo_distros; optional caching for repeated environment checks

The Linux/distro-aware sudo decision is generally sound, but two aspects need attention:

  1. Behavior when sudo_distros is empty
    use_sudo() returns True only when the parsed ID from /etc/os-release is in self.sudo_distros. With an empty sudo_distros, sudo is effectively disabled everywhere. This design choice (strict whitelist vs. optional filter) should be documented explicitly in the class docstring or parameter help. Clarify whether empty sudo_distros means "never use sudo for distro filtering" (whitelist) or "apply no distro filtering" (filter-only).

  2. Repeated environment checks (optional optimization)
    Each SysinfoCommand instance calls can_sudo(), and use_sudo() rereads /etc/os-release. If instances are created frequently, consider caching these at the class level to reduce repeated system calls.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9461cbc and 0887038.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • avocado/plugins/sysinfo.py
  • avocado/etc/avocado/sysinfo.conf
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/core/sysinfo.py (2)
avocado/core/settings.py (1)
  • read (303-321)
avocado/utils/sysinfo.py (1)
  • Command (129-204)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: Egg task ubi:8.8
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Podman Egg task
  • GitHub Check: Fedora selftests
  • GitHub Check: Build Package (wheel/tarball) for Python 3.9
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (2)
avocado/core/sysinfo.py (1)

175-201: Propagation of sudo configuration into Command collectibles looks correct

Passing self.sudo_commands / self.sudo_distros into all sysinfo.Command instances for start, end, and fail commands is consistent and keeps the behavior centralized in Command.collect(). The change is backward compatible (new parameters are optional) and doesn’t affect Logfile/Daemon collectibles.

avocado/utils/sysinfo.py (1)

21-29: Shared avocado.sysinfo logger is appropriate

Using logging.getLogger("avocado.sysinfo") here aligns this module’s logging with avocado.core.sysinfo and keeps sysinfo‑related messages grouped under a single logger name. No issues spotted.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
avocado/utils/sysinfo.py (1)

179-185: Consider reusing SysinfoCommand / caching sudo decision to avoid repeated checks

Right now each Command.collect() with sudo config creates a new SysinfoCommand, which in turn calls can_sudo() and re-parses /etc/os-release. For environments with many sysinfo commands this can become unnecessarily expensive.

You could instead:

  • Build a single SysinfoCommand instance once per SysInfo and pass it (or a precomputed sudo_flag/use_sudo() result) into each Command, or
  • Lazily cache use_sudo()’s result inside SysinfoCommand after the first call.

Functionality is correct as-is; this is a performance/cleanliness improvement.

Also applies to: 194-195

avocado/etc/avocado/sysinfo.conf (1)

1-5: Config snippet aligns with parsing logic; consider clarifying distro IDs

The [sysinfo] section with comma‑separated sudo_commands / sudo_distros matches the CSV parsing in SysInfo.__init__ and gives sane defaults for UOS/Deepin.

For extra clarity to users editing this file, you might extend the sudo_distros comment to mention that values should match the ID= field from /etc/os-release (e.g. uos, deepin), since that’s what SysinfoCommand.use_sudo() checks.

avocado/core/sysinfo.py (1)

18-19: Config loading for sudo options is flexible; watch out for multi-file edge cases

The _load_sudo_list() helper nicely supports both:

  • treating sysinfo.sudo_commands / sysinfo.sudo_distros as paths to INI files with a [sysinfo] section, and
  • treating them directly as comma‑separated lists.

Lowercasing and trimming into sets gives clean inputs to SysinfoCommand.

One minor subtlety: because a single ConfigParser instance is reused, calling _load_sudo_list() for two different files that both define [sysinfo] (but only one of them defines a given key) can lead to values “leaking” from the first file into the second. If you expect users to ever point these two settings at different files, consider instantiating a fresh ConfigParser inside _load_sudo_list() so each path is independent:

-        config = configparser.ConfigParser()
-
-        def _load_sudo_list(raw_value, key):
+        def _load_sudo_list(raw_value, key):
             """
             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):
-                config.read(raw_value)
-                return config.get("sysinfo", key, fallback="")
+                parser = configparser.ConfigParser()
+                parser.read(raw_value)
+                return parser.get("sysinfo", key, fallback="")
             return raw_value

Not a blocker, but this would make the behavior easier to reason about.

Also applies to: 72-112

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0887038 and 5f3a8a0.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (4 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • avocado/plugins/sysinfo.py
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (129-206)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Version task ubuntu:24.04
  • GitHub Check: Fedora develop install/uninstall task
  • GitHub Check: Fedora selftests
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task fedora:41
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Code Coverage (3.11)
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
🔇 Additional comments (4)
avocado/utils/sysinfo.py (3)

21-29: Logger setup looks consistent and appropriate

Using the shared "avocado.sysinfo" logger here matches avocado/core/sysinfo.py and keeps sysinfo logging centralized. No issues seen.


138-147: Constructor extension keeps backward compatibility

Adding sudo_commands and sudo_distros as keyword-only-ish tail parameters, and defaulting them to None, preserves existing Command call sites while allowing the new behavior. The instance attributes are straightforward and safe.


415-462: Harden SysinfoCommand initialization for cross-platform safety

One targeted improvement would make this more robust:

  • Cross‑platform safety: __init__() unconditionally calls can_sudo(). On platforms without Unix-like UID support (e.g., Windows), if can_sudo() internally uses os.getuid() without guards, it will raise AttributeError. Wrap this call defensively:
-    def __init__(self, sudo_commands=None, sudo_distros=None):
-        from avocado.utils.process import can_sudo
-        self.sudo_available = can_sudo()
+    def __init__(self, sudo_commands=None, sudo_distros=None):
+        # Only attempt sudo capability checks on Linux-like systems
+        system_name = platform.system().lower()
+        try:
+            self.sudo_available = (
+                system_name == "linux" and process.can_sudo()
+            )
+        except Exception:  # be defensive against unexpected OS-specific issues
+            self.sudo_available = False

The other suggestions (avoiding local import and caching the result) are reasonable optimizations, but their necessity depends on whether process is imported at module level and how can_sudo() is implemented—details that should be verified against the actual codebase.

avocado/core/sysinfo.py (1)

173-209: Sudo configuration is correctly threaded into collectible commands

Passing self.sudo_commands and self.sudo_distros into sysinfo.Command for commands and fail_commands ensures the new sudo logic is applied uniformly at start, end, and fail‑end phases. Daemon/file collectibles remain unaffected, which is sensible.

This wiring looks correct and keeps the feature nicely localized in Command.collect() / SysinfoCommand.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
avocado/etc/avocado/sysinfo.conf (1)

1-6: Config values align with sudo logic; clarify final comment wording

The defaults for sudo_commands and sudo_distros are consistent with how SysinfoCommand parses values (CSV lists later normalized to lowercase). The last comment is a bit meta (“comment to mention that…”); consider making it a direct statement for clarity.

-# sudo_distros comment to mention that values should match the ID= field from /etc/os-release (e.g. uos, deepin)
+# Values of sudo_distros must match the ID= field from /etc/os-release (e.g. uos, deepin).
avocado/core/sysinfo.py (1)

72-110: Sudo list loader correctly supports INI and CSV; handle missing files more explicitly

The _load_sudo_list() helper nicely supports both “path to INI with [sysinfo] section” and raw CSV values, and the normalization into lowercase sets looks good. One corner case is when a value is meant to be a file path (as per plugin help) but the file doesn’t exist: it falls through to return raw_value, which then gets split as CSV, producing set entries based on the path string instead of cleanly disabling sudo.

To avoid silently misconfiguring sudo behavior when the configured file is missing or mistyped, consider treating a non-existent path as “no config” (and logging it) instead of as CSV:

         def _load_sudo_list(raw_value, key):
@@
-            if not raw_value:
-                return ""
-            if os.path.isfile(raw_value):
-                config = configparser.ConfigParser()
-                config.read(raw_value)
-                return config.get("sysinfo", key, fallback="")
-            return raw_value
+            if not raw_value:
+                return ""
+            if os.path.isfile(raw_value):
+                cfg = configparser.ConfigParser()
+                cfg.read(raw_value)
+                return cfg.get("sysinfo", key, fallback="")
+            # If the value looks like a path but the file is missing, log and ignore.
+            if os.sep in raw_value:
+                log.warning(
+                    "sudo list file for %s not found at %r; ignoring sudo configuration",
+                    key,
+                    raw_value,
+                )
+                return ""
+            # Otherwise, treat it as a raw CSV value.
+            return raw_value
avocado/plugins/sysinfo.py (1)

172-189: Sudo config options are now path-based and consistent; consider clarifying sudo_distros help

Registering sysinfo.sudo_commands / sysinfo.sudo_distros as file paths (with system_wide_or_base_path defaults) matches how SysInfo.__init__() loads the lists and fixes the previous value-vs-path confusion. To make distro matching behavior more discoverable, you might hint in the help text that entries correspond to /etc/os-release ID values.

-        help_msg = "File with list of distributions that require sudo"
+        help_msg = (
+            "File with list of distributions (values matching ID= in /etc/os-release) "
+            "that require sudo"
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f3a8a0 and 98b94c8.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (4 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/plugins/sysinfo.py (2)
avocado/core/utils/path.py (2)
  • system_wide_or_base_path (15-47)
  • prepend_base_path (7-12)
avocado/core/settings.py (3)
  • register_option (646-803)
  • section (138-139)
  • key (142-143)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (129-206)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (36)
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Egg task ubi:8.8
  • GitHub Check: Podman Egg task
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Windows with Python 3.12
  • GitHub Check: Fedora selftests
  • GitHub Check: Windows with Python 3.11
  • GitHub Check: Windows with Python 3.9
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task debian:12.4
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (4)
avocado/core/sysinfo.py (1)

181-207: Passing sudo configuration into all Command collectibles is consistent

Wiring self.sudo_commands and self.sudo_distros into every sysinfo.Command created for normal, end, and fail commands ensures the new sudo logic is applied uniformly without changing existing call sites or Daemon/Logfile behavior. This keeps the API backward compatible while enabling the new feature.

avocado/utils/sysinfo.py (3)

21-29: Adding a dedicated sysinfo logger is appropriate

Using logging.getLogger("avocado.sysinfo") here aligns with the core sysinfo module and gives clear visibility into command execution and sudo decisions without changing behavior elsewhere.


138-148: Command’s sudo integration looks correct and remains backward compatible

Extending Command.__init__ with optional sudo_commands and sudo_distros keeps existing call sites valid while enabling sudo-aware execution. The collect() logic correctly:

  • Derives a fresh environment and normalizes timeout as before.
  • Computes sudo_flag only when both sudo lists are non-empty.
  • Delegates “should this use sudo?” to SysinfoCommand and passes sudo=sudo_flag to process.run() without mutating self.cmd.
  • Logs whether sudo is being used per command.

This is a clean integration point for the new configuration-driven behavior.

Also applies to: 179-185, 194-195


415-463: SysinfoCommand encapsulates sudo decisions cleanly; behavior is conservative outside configured distros

SysinfoCommand neatly centralizes sudo decisions:

  • use_sudo() respects can_sudo(), only applies on Linux, avoids sudo when already root, and restricts sudo usage to distros whose /etc/os-release ID is in the configured sudo_distros set.
  • is_sudo_cmd() safely parses the first token via shlex.split, matches by basename, and ignores malformed commands without raising.

This ensures that only explicitly configured commands on explicitly configured distros gain sudo, leaving other environments unaffected.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
avocado/core/sysinfo.py (2)

72-111: Sudo config parsing (CSV vs INI) looks correct; consider minor UX/logging tweaks

This nicely supports both:

  • direct CSV values in sysinfo.sudo_commands / sysinfo.sudo_distros, and
  • pointing those options at an INI file with a [sysinfo] section.

Two small polish suggestions:

  1. The comment on Line 72 still calls these “paths”; they can now be either CSV values or paths. Updating the wording would avoid confusion.
  2. In _load_sudo_list(), if raw_value looks like a path (contains a path separator or ends in .conf) but os.path.isfile(raw_value) is false, you silently treat it as CSV. Emitting a warning (or at least a debug log) in that case would help users spot typos in file paths.

Optionally, logging the final resolved self.sudo_commands / self.sudo_distros at debug level could also aid troubleshooting.


178-207: Wiring sudo config into Command is aligned with the plugin‑vs‑utils split

Passing sudo_commands and sudo_distros from SysInfo into sysinfo.Command keeps all config parsing in core/sysinfo and leaves utils/sysinfo with plain runtime data, which matches the guidance to avoid config access in utils.

If you want to reduce duplication, you could factor the shared kwargs into a small dict used for all three Command(...) calls, but that’s purely cosmetic.

avocado/utils/sysinfo.py (3)

138-147: Command constructor extension is backward‑compatible; consider updating the docstring

Extending Command.__init__ with sudo_commands=None, sudo_distros=None keeps positional and keyword callers compatible and simply stores the new config on the instance. Equality/hash continue to be based on (cmd, log_path), which is consistent with prior behavior.

You might want to extend the class docstring to briefly mention the new sudo‑related parameters so the behavior is discoverable from the API docs.


179-185: Sudo flag integration is correct; consider avoiding repeated capability checks

The logic of:

  • requiring both non‑empty sudo_commands and sudo_distros,
  • checking use_sudo() for the current distro, and
  • then filtering by is_sudo_cmd(self.cmd)

is sound and should avoid surprising sudo usage.

Because SysinfoCommand.__init__ currently calls can_sudo(), this path will run the sudo capability check for every Command.collect() invocation. If the sysinfo configuration runs many commands, this could add noticeable overhead.

A small optimization (optional):

  • Instantiate a single SysinfoCommand per SysInfo instance (or even per process) and pass it into Command, or
  • Precompute a use_sudo_for_this_distro boolean once and have Command.collect() only run is_sudo_cmd() per call.

That keeps behavior the same while reducing redundant calls to can_sudo() and /etc/os-release parsing.

Also applies to: 194-194


448-463: Command name matching relies on bare program names

is_sudo_cmd() extracts the first token via shlex.split() and compares its basename (lowercased) against self.sudo_cmds. This works well if configuration values are simple program names (dmidecode, fdisk).

Just be aware that entries like /usr/sbin/dmidecode in sudo_commands will not match, because the config is not normalized through os.path.basename() the way runtime commands are. If you anticipate users specifying full paths, you might want to normalize config values similarly:

-        self.sudo_cmds = sudo_commands if sudo_commands else set()
+        self.sudo_cmds = {
+            os.path.basename(cmd).lower() for cmd in (sudo_commands or set())
+        }

Not strictly necessary if you document that only bare command names are supported, but worth considering.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98b94c8 and 61e559a.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • avocado/etc/avocado/sysinfo.conf
  • avocado/plugins/sysinfo.py
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (129-206)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (40)
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora selftests
  • GitHub Check: Build Package (wheel/tarball) for Python 3.9
  • GitHub Check: Build Package (wheel/tarball) for Python 3.10
  • GitHub Check: Build Package (wheel/tarball) for Python 3.11
  • GitHub Check: Version task ubuntu:24.04
  • GitHub Check: Version task fedora:41
  • GitHub Check: Egg task ubi:8.8
  • GitHub Check: Version task fedora:40
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Windows with Python 3.12
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (2)
avocado/core/sysinfo.py (1)

18-18: ConfigParser import usage is appropriate

configparser is used only inside the new sudo config helper and is scoped correctly; no issues here.

avocado/utils/sysinfo.py (1)

21-29: Logger and imports for sysinfo utilities look good

Adding platform, logging, and a shared log = logging.getLogger("avocado.sysinfo") here is appropriate and keeps sysinfo‑related logging under a single namespace across core and utils.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
avocado/utils/sysinfo.py (3)

139-148: Extend Command docstring to cover sudo-related parameters

The constructor now accepts sudo_commands and sudo_distros, but the class docstring and parameter docs still only mention cmd, timeout, and locale. Documenting the new configuration makes the public API clearer to callers.

 class Command(Collectible):
@@
-    :param cmd: String with the command.
-    :param timeout: Timeout for command execution.
-    :param locale: Force LANG for sysinfo collection
+    :param cmd: String with the command.
+    :param timeout: Timeout for command execution.
+    :param locale: Force LANG for sysinfo collection.
+    :param sudo_commands: Iterable of command basenames that should run with sudo
+                          (lowercased; e.g. {"dmidecode", "fdisk"}).
+    :param sudo_distros: Iterable of distro IDs (from ID= in /etc/os-release)
+                         on which sudo should be enabled.

180-196: Sudo decision logic is safe; consider clarifying gating and log verbosity

The sudo_flag computation is conservative and preserves existing behavior when sudo config is missing or can_sudo() is false, which is good. A few minor refinements you might consider:

  • At Line 182, if self.sudo_commands and self.sudo_distros: means both lists must be non-empty; otherwise, sudo is always disabled. If you ever want “sudo for all distros” when only commands are configured, you may want to treat a missing/empty sudo_distros as “no distro filtering” instead of “disable sudo entirely”.
  • Instantiating SysinfoCommand inside collect() means you re-run can_sudo() and re-open /etc/os-release per command execution. Given the small number of sysinfo commands this is probably fine, but if it ever becomes a hot path you could create a single SysinfoCommand per SysInfo instance and pass it (or its precomputed sudo_flag/distro decision) into each Command.
  • Logging every command at INFO (Line 185) may be noisy in large jobs; if that becomes a concern, DEBUG or a more targeted log level could be an option.

All of these are behavioral tweaks rather than correctness fixes, so they can be deferred if you’re happy with the current semantics.


416-465: SysinfoCommand logic is sound; normalize sudo lists for robustness

The overall flow in SysinfoCommand looks correct:

  • can_sudo() is only consulted on Linux, and use_sudo() additionally skips sudo when running as root.
  • Distro gating via /etc/os-release and ID= is straightforward.
  • is_sudo_cmd() correctly handles full paths and quoted commands via shlex.split() and os.path.basename().

To make this helper more robust and self-contained across call sites, consider normalizing the configuration once in __init__:

  • Convert both sudo_commands and sudo_distros to lowercase sets, so callers don’t need to remember to pre-normalize values.
 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_cmds = (
+            {c.lower() for c in sudo_commands} if sudo_commands else set()
+        )
+        self.sudo_distros = (
+            {d.lower() for d in sudo_distros} if sudo_distros else set()
+        )

This keeps is_sudo_cmd() and use_sudo() behavior consistent even if future callers pass in non-normalized data.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61e559a and 54e17e5.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • avocado/etc/avocado/sysinfo.conf
  • avocado/core/sysinfo.py
🧰 Additional context used
🧬 Code graph analysis (1)
avocado/plugins/sysinfo.py (2)
avocado/core/utils/path.py (2)
  • system_wide_or_base_path (15-47)
  • prepend_base_path (7-12)
avocado/core/settings.py (3)
  • register_option (646-803)
  • section (138-139)
  • key (142-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Egg task debian:11.0
  • GitHub Check: Fedora selftests
  • GitHub Check: Fedora develop install/uninstall task
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Podman Egg task
  • GitHub Check: Egg task ubuntu:22.04
  • GitHub Check: Egg task ubuntu:24.04
  • GitHub Check: Egg task debian:12.4
  • GitHub Check: Version task fedora:41
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: Windows with Python 3.11
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: Code Coverage (3.11)
  • GitHub Check: Static checks
  • GitHub Check: macOS with Python 3.11
🔇 Additional comments (2)
avocado/plugins/sysinfo.py (1)

172-192: Sudo configuration options now match file-based semantics

Registering sysinfo.sudo_commands and sysinfo.sudo_distros as prepend_base_path options with a default of system_wide_or_base_path("etc/avocado/sysinfo.conf"), and describing them explicitly as “File with list of …”, is consistent with their use as configuration file paths in core sysinfo logic. This resolves the earlier ambiguity where they looked like CSV value options.

avocado/utils/sysinfo.py (1)

21-29: Imports and logger setup look appropriate

Adding platform, logging, and can_sudo plus a dedicated log = logging.getLogger("avocado.sysinfo") is a clean way to support the new sudo logic and logging. The module import of can_sudo combined with Linux-only invocation inside SysinfoCommand keeps cross-platform behavior safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
avocado/utils/sysinfo.py (3)

16-37: Imports block contains unresolved merge conflict and duplicate abc import

The imports section still has conflict markers and duplicated from abc import ABC, abstractmethod, which makes the module invalid and matches the Ruff syntax errors.

You should resolve the conflict and keep only the imports actually needed for the final design (no configparser if SysinfoCommand doesn’t read configs):

-import json
-import os
-import shlex
-import subprocess
-import tempfile
-<<<<<<< HEAD
-import platform
-import logging
-=======
-import configparser
-import platform
-import logging
-from abc import ABC, abstractmethod
->>>>>>> 9759bba9... Fixed the issue in sysinfo where the dmidecode and fdisk - l commands were executed.
-
-from abc import ABC, abstractmethod
+import json
+import os
+import shlex
+import subprocess
+import tempfile
+import platform
+import logging
+from abc import ABC, abstractmethod
@@
-DATA_SIZE = 200000
-log = logging.getLogger("avocado.sysinfo")
+DATA_SIZE = 200000
+log = logging.getLogger("avocado.sysinfo")

172-208: Unresolved conflict and duplicated sudo decision path in Command.collect

The sudo decision logic in Command.collect() is currently split by conflict markers between:

  • A version that uses injected sudo_commands/sudo_distros and guards with if self.sudo_commands and self.sudo_distros:, and
  • A version that constructs SysinfoCommand() with no arguments and lets it read configuration itself.

This both breaks syntax and contradicts the architecture agreed with the reviewer (config should be read in the plugin/core layer, not in utils). You also need sudo_flag defined even when no sudo config is provided.

Recommend resolving the conflict in favor of the injected-settings path and always initializing sudo_flag:

@@
-        if int(self.timeout) <= 0:
-            self.timeout = None
-
-        # Determine whether to run with sudo (do not mutate the command string)
-<<<<<<< HEAD
-        sudo_flag = False
-        if self.sudo_commands and self.sudo_distros:
-            sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
-            sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
-=======
-        sysinfo_cmd = SysinfoCommand()
-        sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
->>>>>>> 9759bba9... Fixed the issue in sysinfo where the dmidecode and fdisk - l commands were executed.
-        log.info("Executing Command%s: %s", " (sudo)" if sudo_flag else "", self.cmd)
+        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.sudo_commands and self.sudo_distros:
+            sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
+            sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
+
+        log.info("Executing Command%s: %s", " (sudo)" if sudo_flag else "", self.cmd)

This keeps the decision logic centralized in SysinfoCommand while respecting the “no direct config reads in utils” guideline.


216-219: Minor: exception message formatting does not interpolate {exc}

Unrelated to the sudo work, but while you’re touching this file: the CollectibleException message here doesn’t interpolate the exception value; it concatenates a literal "{exc}" string:

raise CollectibleException(
    f'Could not execute "{self.cmd}": ' "{exc}"
) from exc

That will produce a message like Could not execute "cmd": {exc} instead of including the real error.

You can fix it with a single f-string:

-            raise CollectibleException(
-                f'Could not execute "{self.cmd}": ' "{exc}"
-            ) from exc
+            raise CollectibleException(
+                f'Could not execute "{self.cmd}": {exc}'
+            ) from exc
🧹 Nitpick comments (2)
avocado/utils/sysinfo.py (2)

146-156: Command signature change looks compatible; ensure sudo parameters are normalized upstream

The extended Command.__init__ signature (sudo_commands=None, sudo_distros=None) is backwards compatible because the new parameters are optional and only stored on the instance.

Just make sure that call sites pass in normalized values (e.g. set of lowercased command basenames and distro IDs) so that SysinfoCommand doesn’t have to guess or re-normalize them. If you already normalize in the plugin or core sysinfo layer, this is fine as-is.


456-493: use_sudo logic is reasonable but can be slightly clarified

Once conflicts are resolved, use_sudo():

  • Immediately returns False if sudo_available is False (non-Linux or no sudo).
  • On Linux, skips sudo when running as root.
  • Checks /etc/os-release and requires ID to be in self.sudo_distros.

This matches the PR intent. You might consider a tiny readability tweak on the root check and logging level, but it’s optional:

-        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
+        if not self.sudo_available:
+            return False
+        system_name = platform.system().lower()
+        if system_name == "linux":
+            if hasattr(os, "geteuid") and os.geteuid() == 0:
+                return False
             try:
-                with open("/etc/os-release", encoding="utf-8") as f:
+                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.")
+            except FileNotFoundError:
+                log.debug("/etc/os-release not found.")
             return False
         return False

Behavior stays the same but the root check is more explicit and keeps logging at debug level for missing /etc/os-release.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54e17e5 and 27d73af.

📒 Files selected for processing (2)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🧰 Additional context used
🪛 Ruff (0.14.8)
avocado/utils/sysinfo.py

21-21: Expected a statement

(invalid-syntax)


21-21: Expected a statement

(invalid-syntax)


21-21: Expected a statement

(invalid-syntax)


21-21: Expected a statement

(invalid-syntax)


24-24: Expected a statement

(invalid-syntax)


24-24: Expected a statement

(invalid-syntax)


24-24: Expected a statement

(invalid-syntax)


24-24: Expected a statement

(invalid-syntax)


24-25: Expected a statement

(invalid-syntax)


29-29: Expected a statement

(invalid-syntax)


29-29: Expected a statement

(invalid-syntax)


29-29: Expected a statement

(invalid-syntax)


29-29: Expected a statement

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-29: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


29-30: Expected an identifier

(invalid-syntax)


188-188: Expected a statement

(invalid-syntax)


188-188: Expected a statement

(invalid-syntax)


188-188: Expected a statement

(invalid-syntax)


188-188: Expected a statement

(invalid-syntax)


189-189: Unexpected indentation

(invalid-syntax)


193-193: Expected a statement

(invalid-syntax)


193-193: Expected a statement

(invalid-syntax)


193-193: Expected a statement

(invalid-syntax)


193-193: Expected a statement

(invalid-syntax)


193-194: Expected a statement

(invalid-syntax)


194-194: Unexpected indentation

(invalid-syntax)


196-196: Expected a statement

(invalid-syntax)


196-196: Expected a statement

(invalid-syntax)


196-196: Expected a statement

(invalid-syntax)


196-196: Expected a statement

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-196: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


196-197: Expected an identifier

(invalid-syntax)


197-197: Unexpected indentation

(invalid-syntax)


429-429: Expected an indented block after class definition

(invalid-syntax)


429-429: Expected a statement

(invalid-syntax)


429-429: Expected a statement

(invalid-syntax)


429-429: Expected a statement

(invalid-syntax)


430-430: Unexpected indentation

(invalid-syntax)


437-437: Expected a statement

(invalid-syntax)


437-437: Expected a statement

(invalid-syntax)


437-437: Expected a statement

(invalid-syntax)


437-437: Expected a statement

(invalid-syntax)


437-438: Expected a statement

(invalid-syntax)


438-438: Unexpected indentation

(invalid-syntax)


454-454: Expected a statement

(invalid-syntax)


454-454: Expected a statement

(invalid-syntax)


454-454: Expected a statement

(invalid-syntax)


454-454: Expected a statement

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-454: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


454-455: Expected an identifier

(invalid-syntax)


456-456: Unexpected indentation

(invalid-syntax)


466-466: Expected a statement

(invalid-syntax)


466-466: Expected a statement

(invalid-syntax)


466-466: Expected a statement

(invalid-syntax)


466-466: Expected a statement

(invalid-syntax)


467-467: Unexpected indentation

(invalid-syntax)


478-478: Expected a statement

(invalid-syntax)


478-478: Expected a statement

(invalid-syntax)


478-478: Expected a statement

(invalid-syntax)


478-478: Expected a statement

(invalid-syntax)


478-479: Expected a statement

(invalid-syntax)


479-479: Unexpected indentation

(invalid-syntax)


490-490: Expected a statement

(invalid-syntax)


490-490: Expected a statement

(invalid-syntax)


490-490: Expected a statement

(invalid-syntax)


490-490: Expected a statement

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-490: Simple statements must be separated by newlines or semicolons

(invalid-syntax)


490-491: Expected an identifier

(invalid-syntax)


491-491: Unexpected indentation

(invalid-syntax)


492-492: unindent does not match any outer indentation level

(invalid-syntax)


493-493: Unexpected indentation

(invalid-syntax)


495-495: unindent does not match any outer indentation level

(invalid-syntax)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task ubi:8.8
🔇 Additional comments (1)
avocado/utils/sysinfo.py (1)

495-510: is_sudo_cmd implementation is robust and aligns with configuration model

is_sudo_cmd() splits the command safely with shlex.split(), handles parsing errors by returning False, normalizes to a lowercase basename, and then checks membership in self.sudo_cmds. This is defensive and exactly what you need given sudo_commands is configured as bare command names.

No changes needed here.

@xianglongfei-8888 xianglongfei-8888 force-pushed the xlf_dev branch 2 times, most recently from e774811 to 43cf978 Compare December 10, 2025 10:14
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
avocado/core/sysinfo.py (1)

86-98: Local variable config shadows outer scope.

The nested function _load_sudo_list uses config as a local variable name (line 95), which could cause confusion with self.config in the enclosing scope and the configparser module import.

Consider renaming for clarity:

         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):
-                config = configparser.ConfigParser()
-                config.read(raw_value)
-                return config.get("sysinfo", key, fallback="")
+                parser = configparser.ConfigParser()
+                parser.read(raw_value)
+                return parser.get("sysinfo", key, fallback="")
             return raw_value
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27d73af and 43cf978.

📒 Files selected for processing (4)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (130-207)
avocado/plugins/sysinfo.py (2)
avocado/core/utils/path.py (2)
  • system_wide_or_base_path (15-47)
  • prepend_base_path (7-12)
avocado/core/settings.py (3)
  • register_option (646-803)
  • section (138-139)
  • key (142-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (45)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Egg task fedora:40
  • GitHub Check: Egg task fedora:41
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Podman Egg task
  • GitHub Check: Fedora selftests
  • GitHub Check: Build Package (wheel/tarball) for Python 3.9
  • GitHub Check: Build Package (wheel/tarball) for Python 3.11
  • GitHub Check: Build Package (wheel/tarball) for Python 3.10
  • GitHub Check: Build Package (wheel/tarball) for Python 3.13
  • GitHub Check: Egg task debian:11.0
  • GitHub Check: Build Package (wheel/tarball) for Python 3.12
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Version task ubi:8.8
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Static checks
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (6)
avocado/etc/avocado/sysinfo.conf (1)

1-6: LGTM! Configuration file is well-structured.

The merge conflicts flagged in the previous review have been resolved. The file now cleanly defines sudo_commands and sudo_distros under the [sysinfo] section with appropriate guidance comments.

avocado/core/sysinfo.py (1)

179-207: LGTM! Sudo configuration properly propagated to Command instances.

The sudo_commands and sudo_distros are consistently passed to all Command instantiations for start, end, and fail collectibles, enabling sudo-aware command execution across the sysinfo lifecycle.

avocado/plugins/sysinfo.py (1)

172-192: LGTM! Settings registration aligns with the dual-mode loading approach.

The help messages now correctly describe these as "File with list of..." options, and the _load_sudo_list helper in avocado/core/sysinfo.py handles both scenarios (file path or direct CSV value), addressing the previous review concern about config model confusion.

avocado/utils/sysinfo.py (3)

180-185: Clarify: Both sudo_commands AND sudo_distros must be configured for sudo to apply.

The condition on line 182 requires both sudo_commands and sudo_distros to be non-empty for sudo logic to engage. If a user configures only sudo_commands without sudo_distros (or vice versa), sudo will never be used.

This may be intentional (requiring explicit configuration of both), but consider whether an OR condition or separate checks might be more user-friendly, especially if a user forgets to configure one of them.

Please confirm this is the intended behavior. If so, consider adding a debug log when only one is configured to help users troubleshoot:

         # Determine whether to run with sudo (do not mutate the command string)
         sudo_flag = False
         if self.sudo_commands and self.sudo_distros:
             sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
             sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
+        elif self.sudo_commands or self.sudo_distros:
+            log.debug("Sudo not applied: both sudo_commands and sudo_distros must be configured")
         log.info("Executing Command%s: %s", " (sudo)" if sudo_flag else "", self.cmd)

416-448: LGTM! SysinfoCommand properly consumes injected configuration.

The class no longer parses configuration internally, addressing the previous review concern. It correctly:

  • Only checks can_sudo() on Linux systems
  • Skips sudo when running as root (geteuid() == 0)
  • Parses /etc/os-release to match the distro ID against the configured list
  • Handles missing /etc/os-release gracefully with a debug log

450-465: LGTM! Command matching is robust.

The is_sudo_cmd method correctly:

  • Handles empty or malformed commands via exception handling
  • Extracts the base command name (handles full paths like /usr/sbin/dmidecode)
  • Performs case-insensitive comparison

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
avocado/utils/sysinfo.py (1)

179-195: Performance: SysinfoCommand instantiated on every collect() call invokes can_sudo() repeatedly.

SysinfoCommand.__init__ calls can_sudo(), which spawns a subprocess (sudo id -u) to check sudo capability. Creating a new instance on each command execution is inefficient, especially when collecting many sysinfo commands.

Consider instantiating SysinfoCommand once in Command.__init__ or caching the can_sudo() result:

     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
+        # Pre-compute sudo helper once to avoid repeated can_sudo() calls
+        self._sysinfo_cmd = None
+        if self.sudo_commands and self.sudo_distros:
+            self._sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)

Then in collect():

         # Determine whether to run with sudo (do not mutate the command string)
         sudo_flag = False
-        if self.sudo_commands and self.sudo_distros:
-            sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
-            sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
+        if self._sysinfo_cmd:
+            sudo_flag = self._sysinfo_cmd.use_sudo() and self._sysinfo_cmd.is_sudo_cmd(self.cmd)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43cf978 and e88824f.

📒 Files selected for processing (2)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (130-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (46)
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: Fedora selftests
  • GitHub Check: Windows with Python 3.10
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: Windows with Python 3.9
  • GitHub Check: Windows with Python 3.12
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Analyze (python)
  • GitHub Check: Code Coverage (3.11)
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
🔇 Additional comments (5)
avocado/core/sysinfo.py (1)

220-249: LGTM!

The Command instantiations correctly pass sudo_commands and sudo_distros to all collectible types (start, end, and end_fail), maintaining consistency with the updated API in avocado/utils/sysinfo.py.

avocado/utils/sysinfo.py (4)

16-29: LGTM!

The new imports (logging, platform, can_sudo) and module-level logger are appropriate for the sudo detection functionality. Logger name "avocado.sysinfo" is consistent with the core module.


139-148: LGTM!

The addition of sudo_commands and sudo_distros parameters with None defaults maintains backward compatibility while enabling the new sudo functionality.


416-448: LGTM with minor observation.

The SysinfoCommand class correctly:

  • Accepts injected configuration (no config parsing in utils layer, addressing prior review feedback)
  • Gates can_sudo() to Linux only
  • Checks root status via geteuid() before reading /etc/os-release
  • Handles missing /etc/os-release gracefully

450-465: LGTM!

The is_sudo_cmd method correctly parses the command using shlex.split, extracts the base command name, and handles edge cases (empty/malformed commands) via exception handling.

Comment on lines 72 to 111
# 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):
config = configparser.ConfigParser()
config.read(raw_value)
return config.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()
}

# 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):
config = configparser.ConfigParser()
config.read(raw_value)
return config.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()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate code block must be removed.

Lines 72–111 and lines 113–152 are exact duplicates. This appears to be a merge/rebase error. The entire block—config retrieval, logging, _load_sudo_list function definition, and set comprehensions—is repeated twice, causing:

  • Redundant file reads and config parsing
  • Re-definition of _load_sudo_list (shadowing the first)
  • Overwriting self.sudo_commands and self.sudo_distros with identical values

Remove lines 113–152 entirely.

         self.sudo_distros = {
             dst.strip().lower() for dst in sudo_distros_value.split(",") if dst.strip()
         }
-
-        # 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):
-                config = configparser.ConfigParser()
-                config.read(raw_value)
-                return config.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:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 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):
config = configparser.ConfigParser()
config.read(raw_value)
return config.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()
}
# 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):
config = configparser.ConfigParser()
config.read(raw_value)
return config.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()
}
# 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):
config = configparser.ConfigParser()
config.read(raw_value)
return config.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:
🤖 Prompt for AI Agents
In avocado/core/sysinfo.py around lines 72–152 there is an exact duplicate of
the sudo config loading block (lines 72–111 and 113–152). Remove the second copy
(lines 113–152) so the config retrieval, logging, _load_sudo_list definition,
and the set comprehensions appear only once; ensure any necessary imports used
by that block remain and run tests to confirm no references rely on the removed
duplicate.

@xianglongfei-8888 xianglongfei-8888 force-pushed the xlf_dev branch 2 times, most recently from 4d7fc4f to 94be2e3 Compare December 10, 2025 10:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
avocado/core/sysinfo.py (1)

113-152: Critical: Remove this duplicate code block.

This is an exact duplicate of lines 72–111 (already flagged in previous review). The entire block—config retrieval, logging, _load_sudo_list function definition, and set comprehensions—appears twice, causing redundant processing and function re-definition.

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

-        # 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):
-                config = configparser.ConfigParser()
-                config.read(raw_value)
-                return config.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:
🧹 Nitpick comments (1)
avocado/utils/sysinfo.py (1)

179-195: Consider caching the SysinfoCommand instance.

A new SysinfoCommand is instantiated on every collect() call, which redundantly checks can_sudo() each time. Since sudo_commands and sudo_distros are immutable for the Command instance, consider creating the SysinfoCommand once in __init__ or lazily caching it.

     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 _sudo_helper(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

Then in collect():

-        sudo_flag = False
-        if self.sudo_commands and self.sudo_distros:
-            sysinfo_cmd = SysinfoCommand(self.sudo_commands, self.sudo_distros)
-            sudo_flag = sysinfo_cmd.use_sudo() and sysinfo_cmd.is_sudo_cmd(self.cmd)
+        sudo_flag = False
+        if self._sudo_helper:
+            sudo_flag = self._sudo_helper.use_sudo() and self._sudo_helper.is_sudo_cmd(self.cmd)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e88824f and 94be2e3.

📒 Files selected for processing (2)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (130-207)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (46)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Windows with Python 3.12
  • GitHub Check: Windows with Python 3.9
  • GitHub Check: Egg task fedora:40
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task fedora:41
  • GitHub Check: Version task ubi:9.2
  • GitHub Check: Version task fedora:40
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Version task debian:11.0
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (6)
avocado/utils/sysinfo.py (4)

17-29: LGTM: Imports and logger setup are appropriate.

The new imports (logging, platform, can_sudo) and the module-level logger are correctly scoped for the new sudo-aware functionality.


139-148: LGTM: Clean parameter extension.

The sudo_commands and sudo_distros parameters are correctly added with safe defaults.


416-448: LGTM: SysinfoCommand follows the injected-settings pattern.

The class correctly receives configuration via constructor parameters rather than reading config files directly, addressing the previous review feedback. The use_sudo() logic properly handles root detection, platform checks, and distro matching.


450-465: LGTM: Robust command parsing.

The is_sudo_cmd() method safely parses the command string, handles edge cases with appropriate exception handling, and correctly extracts the command basename for comparison.

avocado/core/sysinfo.py (2)

72-111: LGTM: Sudo configuration loading logic.

The _load_sudo_list helper correctly handles both INI file paths and direct CSV values, providing flexibility for configuration. The logging of config presence is helpful for debugging.


220-249: LGTM: Configuration passed to Command instances.

The sudo_commands and sudo_distros are correctly propagated to all Command instantiations, ensuring consistent sudo behavior across start, end, and fail collectibles.

@xianglongfei-8888 xianglongfei-8888 force-pushed the xlf_dev branch 2 times, most recently from 40f3299 to d5717d9 Compare December 10, 2025 11:10
@xianglongfei-8888
Copy link
Author

Hi @xianglongfei-8888,

This proposal of selectively using sudo makes sense to me. But, I have some comments about the implementation:

  1. The SysinfoCommand class redoes the parsing of Avocado's configuration, and it does so in a way that is not exactly the same. It's better to use avocado.core.settings.settings instead.
  2. A lot of this code does not fall into the "utility" category. Think of it as if that module was standalone for other people to build upon their final functionality. And with the migration of all avocado.utils.* to the AAUtils project, this will need to be taken into account.

So, being more specific, instead of taking configuration in avocado.utils.sysinfo from the configuration file directly, most of the code should be in the sysinfo plugin, which has access to the effective settings. From these settings, pass them as (new?) parameters to avocado.utils.sysinfo.

Let me know if this makes sense to you.

Thanks!

@clebergnu Thanks you for your suggestion! It's excellent, and the refactoring has been completed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
avocado/core/sysinfo.py (1)

86-98: Consider adding exception handling for ConfigParser operations.

The _load_sudo_list helper provides flexible configuration by accepting either file paths or direct CSV strings. However, if the INI file exists but is malformed, parser.read() or parser.get() could raise exceptions that would bubble up uncaught.

Consider wrapping the file-reading logic:

         if os.path.isfile(raw_value):
+            try:
                 parser = configparser.ConfigParser()
                 parser.read(raw_value)
                 return parser.get("sysinfo", key, fallback="")
+            except (configparser.Error, IOError) as e:
+                log.warning("Failed to read sudo config from %s: %s", raw_value, e)
+                return ""
         return raw_value
avocado/plugins/sysinfo.py (2)

172-180: Consider clarifying that the option accepts both file paths and CSV values.

The help text states "File with list of commands that require sudo", but the implementation in avocado/core/sysinfo.py accepts both:

  1. A path to an INI file with a [sysinfo] section
  2. A direct comma-separated list of commands

Update the help text to reflect both modes:

-        help_msg = "File with list of commands that require sudo"
+        help_msg = (
+            "Path to config file with [sysinfo] sudo_commands, "
+            "or comma-separated list of commands that require sudo"
+        )

182-192: Consider clarifying that the option accepts both file paths and CSV values.

Similar to sudo_commands, the help text describes this as a file path only, but the implementation also accepts direct CSV values.

Update the help text for consistency:

         help_msg = (
-            "File with list of distributions (values matching ID= in /etc/os-release) "
+            "Path to config file with [sysinfo] sudo_distros, "
+            "or comma-separated list of distributions (values matching ID= in /etc/os-release) "
             "that require sudo"
         )
avocado/utils/sysinfo.py (1)

431-454: Consider caching os-release parsing and broadening exception handling.

The method reads and parses /etc/os-release on every call. While sysinfo commands aren't run frequently, consider:

  1. Caching: Parse /etc/os-release once in __init__ and store the result
  2. Exception handling: Line 450 only catches FileNotFoundError, but the file could be malformed or have permission issues
     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
+        self._os_id = None
         # Only attempt sudo capability detection on Linux, where it is relevant.
         if platform.system().lower() == "linux":
             self.sudo_available = can_sudo()
+            self._os_id = self._read_os_id()
+    
+    def _read_os_id(self):
+        """Read OS ID from /etc/os-release."""
+        try:
+            with open("/etc/os-release", encoding="utf-8") as f:
+                for line in f:
+                    if line.startswith("ID="):
+                        return line.strip().split("=")[1].strip('"').lower()
+        except (FileNotFoundError, IOError, IndexError, ValueError) as e:
+            log.debug("Could not read OS ID from /etc/os-release: %s", e)
+        return None

     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
+            if self._os_id:
+                return self._os_id in self.sudo_distros
             return False
         return False
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5717d9 and cb9c412.

📒 Files selected for processing (9)
  • avocado/core/sysinfo.py (3 hunks)
  • avocado/etc/avocado/sysinfo.conf (1 hunks)
  • avocado/plugins/sysinfo.py (1 hunks)
  • avocado/utils/sysinfo.py (5 hunks)
  • avocado/utils/wait.py (2 hunks)
  • selftests/check.py (1 hunks)
  • selftests/functional/utils/wait.py (1 hunks)
  • selftests/unit/utils/wait.py (1 hunks)
  • spell.ignore (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • selftests/check.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • avocado/etc/avocado/sysinfo.conf
🧰 Additional context used
🧬 Code graph analysis (5)
selftests/unit/utils/wait.py (1)
avocado/utils/wait.py (1)
  • wait_for (15-104)
selftests/functional/utils/wait.py (2)
selftests/utils.py (1)
  • TestCaseTmpDir (167-174)
avocado/utils/wait.py (1)
  • wait_for (15-104)
avocado/plugins/sysinfo.py (2)
avocado/core/utils/path.py (2)
  • system_wide_or_base_path (15-47)
  • prepend_base_path (7-12)
avocado/core/settings.py (3)
  • register_option (646-803)
  • section (138-139)
  • key (142-143)
avocado/utils/sysinfo.py (1)
avocado/utils/process.py (2)
  • can_sudo (65-86)
  • system (1023-1085)
avocado/core/sysinfo.py (1)
avocado/utils/sysinfo.py (1)
  • Command (130-213)
🔇 Additional comments (13)
spell.ignore (1)

813-815: LGTM!

The additions are appropriate:

  • "truthy" and "falsy" are standard programming terminology
  • "myfile" is used in documentation examples (e.g., wait.py docstring)
avocado/utils/wait.py (2)

1-6: LGTM!

The module docstring clearly explains the purpose and use cases for the wait utilities.


16-85: LGTM!

The expanded documentation is comprehensive and includes excellent examples covering common use cases. The parameter descriptions are detailed and the examples demonstrate practical scenarios.

selftests/unit/utils/wait.py (1)

1-232: LGTM!

Comprehensive unit test coverage for wait.wait_for including:

  • Success scenarios (immediate and delayed)
  • Timeout handling
  • Argument passing (positional and keyword)
  • Logging behavior
  • Edge cases (zero timeout, large step, empty values)
  • Various return types and callable objects

The test suite is well-structured and follows best practices.

selftests/functional/utils/wait.py (1)

1-54: LGTM!

Functional tests provide real-world validation of wait.wait_for behavior with actual I/O operations. The tests appropriately use threading to simulate asynchronous conditions and include reasonable timing tolerances.

avocado/core/sysinfo.py (3)

72-84: LGTM!

Configuration retrieval is properly implemented with informative logging for both presence and absence of settings.


100-111: LGTM!

The sudo configuration values are properly parsed into normalized lowercase sets with whitespace handling.


179-208: LGTM!

The sudo configuration is consistently passed to all Command instances (start_collectibles, end_collectibles, and end_fail_collectibles), ensuring uniform sudo behavior across collection phases.

avocado/utils/sysinfo.py (5)

17-17: LGTM!

The necessary imports and module-level logger are properly added to support the new sudo functionality.

Also applies to: 19-19, 26-26, 29-29


139-155: LGTM!

The Command class properly extends its interface to accept sudo configuration and provides a lazy-initialization pattern via the _sudoer property to create SysinfoCommand instances only when needed.


186-202: LGTM!

The sudo determination logic correctly:

  • Computes sudo_flag without mutating the command string
  • Logs sudo usage for observability
  • Passes the sudo flag to process.run()

422-429: LGTM!

The SysinfoCommand initialization properly:

  • Accepts injected configuration (no direct config reading)
  • Initializes sudo_cmds and sudo_distros as sets with defaults
  • Checks sudo availability only on Linux systems

456-471: LGTM!

The is_sudo_cmd method properly:

  • Uses shlex.split to parse the command correctly
  • Handles empty commands and parsing errors gracefully
  • Normalizes the command basename to lowercase for comparison

…were executed. Also added sysinfo.conf to RPM spec file to resolve packaging error.

Signed-off-by: xianglongfei <xianglongfei@uniontech.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Review Requested

Development

Successfully merging this pull request may close these issues.

2 participants