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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions nodescraper/connection/inband/inbandmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
EventCategory,
EventPriority,
ExecutionStatus,
OSFamily,
SystemLocation,
)
from nodescraper.interfaces.connectionmanager import ConnectionManager
Expand All @@ -43,7 +42,6 @@
from .inband import InBandConnection
from .inbandlocal import LocalShell
from .inbandremote import RemoteShell, SSHConnectionError
from .osdetection import NetworkOsDetection, detect_network_os
from .sshparams import SSHConnectionParams


Expand All @@ -69,52 +67,6 @@ def __init__(
**kwargs,
)

@staticmethod
def _apply_network_os_detection(
system_info: SystemInfo,
detection: NetworkOsDetection,
) -> None:
"""Apply network OS probe results to system info."""
system_info.os_family = detection.os_family
system_info.platform = detection.platform
if system_info.metadata is None:
system_info.metadata = {}
system_info.metadata.update(detection.metadata)

def _check_os_family(self):
"""Check the OS family of the system under test (SUT)

Raises:
RuntimeError: If the connection is not initialized
"""
if not self.connection:
raise RuntimeError("Connection not initialized")

self.logger.info("Checking OS family")
res = self.connection.run_command("uname -s")
if "not recognized as an internal or external command" in res.stdout + res.stderr:
self.system_info.os_family = OSFamily.WINDOWS
elif res.exit_code == 0:
self.system_info.os_family = OSFamily.LINUX
else:
detection = detect_network_os(self.connection)
if detection is not None:
self._apply_network_os_detection(self.system_info, detection)
else:
self._log_event(
category=EventCategory.UNKNOWN,
description="Unable to determine SUT OS",
priority=EventPriority.WARNING,
)
if self.system_info.platform:
self.logger.info(
"OS Family: %s (%s)",
self.system_info.os_family.name,
self.system_info.platform,
)
else:
self.logger.info("OS Family: %s", self.system_info.os_family.name)

def connect(
self,
) -> TaskResult:
Expand All @@ -126,7 +78,6 @@ def connect(
if self.system_info.location == SystemLocation.LOCAL:
self.logger.info("Using local shell")
self.connection = LocalShell()
self._check_os_family()
return self.result

if not self.connection_args or not isinstance(self.connection_args, SSHConnectionParams):
Expand All @@ -150,7 +101,6 @@ def connect(
)
self.connection = RemoteShell(self.connection_args)
self.connection.connect_ssh()
self._check_os_family()
except SSHConnectionError as exception:
self._log_event(
category=EventCategory.SSH,
Expand Down
55 changes: 55 additions & 0 deletions nodescraper/connection/inband/osdetection.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
#
###############################################################################
import json
import logging
import re
from dataclasses import dataclass
from typing import Optional

from nodescraper.connection.inband import InBandConnectionManager
from nodescraper.enums import OSFamily
from nodescraper.models import SystemInfo

from .inband import InBandConnection

Expand Down Expand Up @@ -150,3 +153,55 @@ def detect_network_os(connection: InBandConnection) -> Optional[NetworkOsDetecti
return detection

return None


def apply_network_os_detection(
system_info: SystemInfo,
detection: NetworkOsDetection,
) -> None:
"""Apply network OS probe results to system info."""
system_info.os_family = detection.os_family
system_info.platform = detection.platform
if system_info.metadata is None:
system_info.metadata = {}
system_info.metadata.update(detection.metadata)


def discover_and_write_os_family(
connection_manager: InBandConnectionManager,
system_info: SystemInfo,
logger: logging.Logger,
) -> None:
"""Check

Args:
connection_manager (InBandConnectionManager): _description_
system_info (SystemInfo): _description_
logger (logging.Logger): _description_
"""
if connection_manager.connection is None:
logger.error("Connection is not initialized, OS family check cannot be performed.")
return
logger.info("Checking OS family")
res = connection_manager.connection.run_command("uname -s")
if "not recognized as an internal or external command" in res.stdout + res.stderr:
system_info.os_family = OSFamily.WINDOWS
elif res.exit_code == 0:
system_info.os_family = OSFamily.LINUX
else:
detection = detect_network_os(connection_manager.connection)
if detection is not None:
apply_network_os_detection(system_info, detection)
else:
logger.warning(
"Unable to determine OS family. uname failed and no supported network OS detected."
)

if system_info.platform:
logger.info(
"OS Family: %s (%s)",
system_info.os_family.name,
system_info.platform,
)
else:
logger.info("OS Family: %s", system_info.os_family.name)
34 changes: 30 additions & 4 deletions nodescraper/pluginexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@
from pydantic import BaseModel

from nodescraper.base.oobsshdataplugin import OOBSSHDataPlugin
from nodescraper.connection.inband import InBandConnectionManager
from nodescraper.connection.inband.osdetection import discover_and_write_os_family
from nodescraper.connection.oob_ssh import OobSshConnectionManager
from nodescraper.constants import DEFAULT_LOGGER
from nodescraper.enums import ExecutionStatus
from nodescraper.interfaces import ConnectionManager, DataPlugin, PluginInterface
from nodescraper.interfaces.taskresulthook import TaskResultHook
from nodescraper.models import PluginConfig, SystemInfo
Expand Down Expand Up @@ -104,7 +107,8 @@ def __init__(
for connection, connection_args in connections.items():
if connection not in self.plugin_registry.connection_managers:
self.logger.error(
"Unable to find registered connection manager class for %s", connection
"Unable to find registered connection manager class for %s",
connection,
)
continue

Expand Down Expand Up @@ -161,13 +165,29 @@ def merge_configs(plugin_configs: list[PluginConfig]) -> PluginConfig:

return merged_config

def discover_os_info(self) -> None:
"""If the connection library has an InBandConnectionManager, use it to discover OS info and update system_info
self.system_info will be updated with the discovered OS info.
"""
inband_connection = self.connection_library.get(InBandConnectionManager)
result = inband_connection.connect() if inband_connection else None
if (not inband_connection) or (not result) or (result.status != ExecutionStatus.OK):
self.logger.info(
"InBandConnectionManager not available or failed to connect for OS discovery. Skipping OS discovery."
)
return
discover_and_write_os_family(inband_connection, self.system_info, self.logger)
inband_connection.disconnect()

def run_queue(self) -> list[PluginResult]:
"""Run the plugin queue and return results

Returns:
list[PluginResult]: List of results from running the plugins in the queue
"""
plugin_results = []
# For Plugins discover OS Family
self.discover_os_info()
plugin_queue = deque(self.plugin_config.plugins.items())
try:
while len(plugin_queue) > 0:
Expand Down Expand Up @@ -276,7 +296,9 @@ def run_queue(self) -> list[PluginResult]:
hook(plugin_result)
except Exception as e:
self.logger.exception(
"Unexpected exception when running plugin %s: %s", plugin_name, e
"Unexpected exception when running plugin %s: %s",
plugin_name,
e,
)
except Exception as e:
self.logger.exception("Unexpected exception running plugin queue: %s", str(e))
Expand All @@ -285,11 +307,15 @@ def run_queue(self) -> list[PluginResult]:

if self.plugin_config.result_collators:
self.logger.info("Running result collators")
for collator, collator_args in self.plugin_config.result_collators.items():
for (
collator,
collator_args,
) in self.plugin_config.result_collators.items():
collator_class = self.plugin_registry.result_collators.get(collator)
if collator_class is None:
self.logger.warning(
"No result collator found in registry for name: %s", collator
"No result collator found in registry for name: %s",
collator,
)
continue

Expand Down
2 changes: 0 additions & 2 deletions nodescraper/pluginregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,8 @@ def _load_plugins_uncached() -> dict[str, type]:
"""Internal: Load plugins without caching logic."""
plugins = {}
eps: Iterable = PluginRegistry.load_entry_points(ENTRY_POINT_PLUGINS)

for entry_point in eps:
plugin_class = entry_point.load() # type: ignore[attr-defined, union-attr]

if not PluginRegistry._valid_sub_class_check(
in_cls=plugin_class, base_class=PluginInterface
):
Expand Down
17 changes: 17 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,20 @@ explicit_package_bases = true

[tool.setuptools_scm]
version_scheme = "post-release"

[dependency-groups]
dev = [
"build",
"black",
"pylint",
"coverage",
"twine",
"ruff",
"pre-commit",
"pytest",
"pytest-cov",
"mypy",
"types-paramiko",
"types-requests",
"types-setuptools",
]
Loading