diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index ef3aad04..727c9570 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -154,6 +154,13 @@ def build_parser( help="Generate reference config from system. Writes to ./reference_config.json.", ) + parser.add_argument( + "--skip-sudo", + dest="skip_sudo", + action="store_true", + help="Skip plugins that require sudo permissions", + ) + subparsers = parser.add_subparsers(dest="subcmd", help="Subcommands") summary_parser = subparsers.add_parser( @@ -418,6 +425,11 @@ def main(arg_input: Optional[list[str]] = None): plugin_subparser_map=plugin_subparser_map, ) + if parsed_args.skip_sudo: + plugin_config_inst_list[-1].global_args.setdefault("collection_args", {})[ + "skip_sudo" + ] = True + log_system_info(log_path, system_info, logger) except Exception as e: parser.error(str(e)) diff --git a/nodescraper/plugins/inband/dimm/collector_args.py b/nodescraper/plugins/inband/dimm/collector_args.py new file mode 100644 index 00000000..6e1f6897 --- /dev/null +++ b/nodescraper/plugins/inband/dimm/collector_args.py @@ -0,0 +1,31 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from nodescraper.models import CollectorArgs + + +class DimmCollectorArgs(CollectorArgs): + skip_sudo: bool = False diff --git a/nodescraper/plugins/inband/dimm/dimm_collector.py b/nodescraper/plugins/inband/dimm/dimm_collector.py index 9d854187..a7af7117 100644 --- a/nodescraper/plugins/inband/dimm/dimm_collector.py +++ b/nodescraper/plugins/inband/dimm/dimm_collector.py @@ -23,23 +23,29 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from .collector_args import DimmCollectorArgs from .dimmdata import DimmDataModel -class DimmCollector(InBandDataCollector[DimmDataModel, None]): +class DimmCollector(InBandDataCollector[DimmDataModel, DimmCollectorArgs]): """Collect data on installed DIMMs""" DATA_MODEL = DimmDataModel def collect_data( self, - args=None, + args: Optional[DimmCollectorArgs] = None, ) -> tuple[TaskResult, DimmDataModel | None]: """Collect data on installed DIMMs""" + if args is None: + args = DimmCollectorArgs() + dimm_str = None if self.system_info.os_family == OSFamily.WINDOWS: res = self._run_sut_cmd("wmic memorychip get Capacity") @@ -59,6 +65,10 @@ def collect_data( for capacity, count in capacities.items(): dimm_str += f"{count} x {capacity / 1024 / 1024:.2f}GB " else: + if args.skip_sudo: + self.result.message = "Skipping sudo plugin" + self.result.status = ExecutionStatus.NOT_RAN + return self.result, None res = self._run_sut_cmd( """sh -c 'dmidecode -t 17 | tr -s " " | grep -v "Volatile\\|None\\|Module" | grep Size' 2>/dev/null""", sudo=True, diff --git a/nodescraper/plugins/inband/dimm/dimm_plugin.py b/nodescraper/plugins/inband/dimm/dimm_plugin.py index 0e838fea..30dcc86d 100644 --- a/nodescraper/plugins/inband/dimm/dimm_plugin.py +++ b/nodescraper/plugins/inband/dimm/dimm_plugin.py @@ -25,13 +25,16 @@ ############################################################################### from nodescraper.base import InBandDataPlugin +from .collector_args import DimmCollectorArgs from .dimm_collector import DimmCollector from .dimmdata import DimmDataModel -class DimmPlugin(InBandDataPlugin[DimmDataModel, None, None]): +class DimmPlugin(InBandDataPlugin[DimmDataModel, DimmCollectorArgs, None]): """Plugin for collection and analysis of DIMM data""" DATA_MODEL = DimmDataModel COLLECTOR = DimmCollector + + COLLECTOR_ARGS = DimmCollectorArgs diff --git a/nodescraper/plugins/inband/dmesg/collector_args.py b/nodescraper/plugins/inband/dmesg/collector_args.py new file mode 100644 index 00000000..4863ad0f --- /dev/null +++ b/nodescraper/plugins/inband/dmesg/collector_args.py @@ -0,0 +1,31 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from nodescraper.models import CollectorArgs + + +class DmesgCollectorArgs(CollectorArgs): + skip_sudo: bool = False diff --git a/nodescraper/plugins/inband/dmesg/dmesg_collector.py b/nodescraper/plugins/inband/dmesg/dmesg_collector.py index 4f07ba11..9be59793 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_collector.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_collector.py @@ -23,14 +23,17 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector -from nodescraper.enums import EventCategory, EventPriority, OSFamily +from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from .collector_args import DmesgCollectorArgs from .dmesgdata import DmesgData -class DmesgCollector(InBandDataCollector[DmesgData, None]): +class DmesgCollector(InBandDataCollector[DmesgData, DmesgCollectorArgs]): """Read dmesg log""" SUPPORTED_OS_FAMILY = {OSFamily.LINUX} @@ -60,13 +63,21 @@ def _get_dmesg_content(self) -> str: def collect_data( self, - args=None, + args: Optional[DmesgCollectorArgs] = None, ) -> tuple[TaskResult, DmesgData | None]: """Collect dmesg data from the system Returns: tuple[TaskResult, DmesgData | None]: tuple containing the result of the task and the dmesg data if available """ + if args is None: + args = DmesgCollectorArgs() + + if args.skip_sudo: + self.result.message = "Skipping sudo plugin" + self.result.status = ExecutionStatus.NOT_RAN + return self.result, None + dmesg_content = self._get_dmesg_content() if dmesg_content: diff --git a/nodescraper/plugins/inband/dmesg/dmesg_plugin.py b/nodescraper/plugins/inband/dmesg/dmesg_plugin.py index 180ae004..b1b42900 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_plugin.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_plugin.py @@ -26,12 +26,13 @@ from nodescraper.base import InBandDataPlugin from .analyzer_args import DmesgAnalyzerArgs +from .collector_args import DmesgCollectorArgs from .dmesg_analyzer import DmesgAnalyzer from .dmesg_collector import DmesgCollector from .dmesgdata import DmesgData -class DmesgPlugin(InBandDataPlugin[DmesgData, None, DmesgAnalyzerArgs]): +class DmesgPlugin(InBandDataPlugin[DmesgData, DmesgCollectorArgs, DmesgAnalyzerArgs]): """Plugin for collection and analysis of dmesg data""" DATA_MODEL = DmesgData @@ -39,3 +40,5 @@ class DmesgPlugin(InBandDataPlugin[DmesgData, None, DmesgAnalyzerArgs]): COLLECTOR = DmesgCollector ANALYZER = DmesgAnalyzer + + COLLECTOR_ARGS = DmesgCollectorArgs diff --git a/nodescraper/plugins/inband/kernel/kernel_collector.py b/nodescraper/plugins/inband/kernel/kernel_collector.py index 6e9ec256..cdb60c23 100644 --- a/nodescraper/plugins/inband/kernel/kernel_collector.py +++ b/nodescraper/plugins/inband/kernel/kernel_collector.py @@ -53,7 +53,7 @@ def collect_data( "=" )[1] else: - res = self._run_sut_cmd("sh -c 'uname -r'", sudo=True) + res = self._run_sut_cmd("sh -c 'uname -r'") if res.exit_code == 0: kernel = res.stdout diff --git a/nodescraper/plugins/inband/os/os_collector.py b/nodescraper/plugins/inband/os/os_collector.py index 47dadc51..2a95d84f 100644 --- a/nodescraper/plugins/inband/os/os_collector.py +++ b/nodescraper/plugins/inband/os/os_collector.py @@ -84,8 +84,7 @@ def collect_data(self, args=None) -> tuple[TaskResult, OsDataModel | None]: else: PRETTY_STR = "PRETTY_NAME" # noqa: N806 res = self._run_sut_cmd( - f"sh -c '( lsb_release -ds || (cat /etc/*release | grep {PRETTY_STR}) || uname -om ) 2>/dev/null | head -n1'", - sudo=False, + f"sh -c '( lsb_release -ds || (cat /etc/*release | grep {PRETTY_STR}) || uname -om ) 2>/dev/null | head -n1'" ) # search for PRETTY_NAME in res if res.exit_code == 0: diff --git a/nodescraper/plugins/inband/storage/collector_args.py b/nodescraper/plugins/inband/storage/collector_args.py new file mode 100644 index 00000000..3067f6a8 --- /dev/null +++ b/nodescraper/plugins/inband/storage/collector_args.py @@ -0,0 +1,31 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from nodescraper.models import CollectorArgs + + +class StorageCollectorArgs(CollectorArgs): + skip_sudo: bool = False diff --git a/nodescraper/plugins/inband/storage/storage_collector.py b/nodescraper/plugins/inband/storage/storage_collector.py index 0e9eb79a..30cf2163 100644 --- a/nodescraper/plugins/inband/storage/storage_collector.py +++ b/nodescraper/plugins/inband/storage/storage_collector.py @@ -24,11 +24,13 @@ # ############################################################################### import re +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from .collector_args import StorageCollectorArgs from .storagedata import DeviceStorageData, StorageDataModel @@ -37,8 +39,13 @@ class StorageCollector(InBandDataCollector[StorageDataModel, None]): DATA_MODEL = StorageDataModel - def collect_data(self, args: None = None) -> tuple[TaskResult, StorageDataModel | None]: + def collect_data( + self, args: Optional[StorageCollectorArgs] = None + ) -> tuple[TaskResult, StorageDataModel | None]: """read storage usage data""" + if args is None: + args = StorageCollectorArgs() + storage_data = {} if self.system_info.os_family == OSFamily.WINDOWS: res = self._run_sut_cmd( @@ -55,6 +62,10 @@ def collect_data(self, args: None = None) -> tuple[TaskResult, StorageDataModel percent=round((int(size) - int(free_space)) / int(size) * 100, 2), ) else: + if args.skip_sudo: + self.result.message = "Skipping sudo plugin" + self.result.status = ExecutionStatus.NOT_RAN + return self.result, None res = self._run_sut_cmd("""sh -c 'df -lH -B1 | grep -v 'boot''""", sudo=True) if res.exit_code == 0: for line in res.stdout.splitlines()[1:]: diff --git a/nodescraper/plugins/inband/storage/storage_plugin.py b/nodescraper/plugins/inband/storage/storage_plugin.py index f0848005..1687e20e 100644 --- a/nodescraper/plugins/inband/storage/storage_plugin.py +++ b/nodescraper/plugins/inband/storage/storage_plugin.py @@ -26,12 +26,13 @@ from nodescraper.base import InBandDataPlugin from .analyzer_args import StorageAnalyzerArgs +from .collector_args import StorageCollectorArgs from .storage_analyzer import StorageAnalyzer from .storage_collector import StorageCollector from .storagedata import StorageDataModel -class StoragePlugin(InBandDataPlugin[StorageDataModel, None, StorageAnalyzerArgs]): +class StoragePlugin(InBandDataPlugin[StorageDataModel, StorageCollectorArgs, StorageAnalyzerArgs]): """Plugin for collection and analysis of disk usage data""" DATA_MODEL = StorageDataModel @@ -39,3 +40,5 @@ class StoragePlugin(InBandDataPlugin[StorageDataModel, None, StorageAnalyzerArgs COLLECTOR = StorageCollector ANALYZER = StorageAnalyzer + + COLLECTOR_ARGS = StorageCollectorArgs