Skip to content
Merged
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
27 changes: 21 additions & 6 deletions docs/PLUGIN_DOC.md

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion docs/generate_plugin_doc_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,12 +505,25 @@ def extract_collection_args_from_collector_args(args_cls: Optional[type]) -> Lis
return output


def escape_lone_markdown_asterisks(s: str) -> str:
"""Escape single asterisks so GFM does not start emphasis spans; preserve **bold** and `code`."""
parts = re.split(r"(`[^`]*`)", s)
out: list[str] = []
for index, part in enumerate(parts):
if index % 2 == 1:
out.append(part)
else:
out.append(re.sub(r"(?<!\*)\*(?!\*)", r"\\*", part))
return "".join(out)


def escape_table_cell(s: str) -> str:
"""Escape content for a markdown table cell so pipes and newlines don't break columns.
Use HTML entity for pipe so all markdown parsers treat it as content, not column separator.
"""
if not s:
return s
s = escape_lone_markdown_asterisks(s)
# Avoid @ in cells (e.g. OData property names) being turned into mail/mention links in Outlook/HTML viewers.
return s.replace("|", "&#124;").replace("@", "&#64;").replace("\n", " ").replace("\r", " ")

Expand All @@ -524,7 +537,9 @@ def md_kv(key: str, value: str) -> str:


def md_list(items: List[str]) -> str:
return "".join(f"- {i}\n" for i in items) + ("\n" if items else "")
return "".join(f"- {escape_lone_markdown_asterisks(i)}\n" for i in items) + (
"\n" if items else ""
)


def bases_list(cls: type) -> List[str]:
Expand Down
2 changes: 2 additions & 0 deletions nodescraper/plugins/inband/dmesg/dmesg_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ class DmesgPlugin(InBandDataPlugin[DmesgData, DmesgCollectorArgs, DmesgAnalyzerA

ANALYZER = DmesgAnalyzer

ANALYZER_ARGS = DmesgAnalyzerArgs

COLLECTOR_ARGS = DmesgCollectorArgs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class RegexSearchPlugin(InBandDataPlugin[RegexSearchData, CollectorArgs, RegexSe
ANALYZER = RegexSearchAnalyzer
ANALYZER_ARGS = RegexSearchAnalyzerArgs

DOCUMENTATION_COLLECTION_ITEMS: tuple[str, ...] = (
"No collector step: reads local text from the CLI --data path (file or directory).",
"Directory scans load each file's contents into RegexSearchData for analysis.",
)

DOCUMENTATION_ANALYSIS_ITEMS: tuple[str, ...] = (
"Runs RegexSearchAnalyzer: user-defined patterns via analysis_args.error_regex (same shape as Dmesg).",
"Emits regex match events with optional per-file source in the description when scanning directories.",
Expand Down
2 changes: 2 additions & 0 deletions nodescraper/plugins/inband/storage/storage_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ class StoragePlugin(InBandDataPlugin[StorageDataModel, StorageCollectorArgs, Sto

ANALYZER = StorageAnalyzer

ANALYZER_ARGS = StorageAnalyzerArgs

COLLECTOR_ARGS = StorageCollectorArgs
4 changes: 2 additions & 2 deletions nodescraper/plugins/ooband/redfish_endpoint/analyzer_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ class RedfishEndpointAnalyzerArgs(AnalyzerArgs):
checks: dict[str, dict[str, RedfishConstraint]] = Field(
default_factory=dict,
description=(
"Map: URI or '*' -> { property_path: constraint }. "
"Map: URI or `*` -> { property_path: constraint }. "
"URI keys must match a key in the collected responses (exact match). "
"Use '*' as the key to apply the inner constraints to every collected response body. "
"Use `*` as the key to apply the inner constraints to every collected response body. "
"Property paths use '/' for nesting and indices, e.g. 'Status/Health', 'PowerControl/0/PowerConsumedWatts'. "
"Constraints: "
"'eq' — value must equal the given literal (int, float, str, bool). "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class RedfishEndpointAnalyzer(DataAnalyzer[RedfishEndpointDataModel, RedfishEndp
DOCUMENTATION_ANALYSIS_ITEMS: tuple[str, ...] = (
"For each entry in analysis_args.checks, reads JSON paths in collected responses and "
"compares values to constraints (eq, min/max, anyOf, regex, etc.).",
'URI key "*" runs checks against every collected response body.',
"URI key `*` runs checks against every collected response body.",
)

def analyze_data(
Expand Down
7 changes: 7 additions & 0 deletions nodescraper/plugins/serviceability/mi3xx/mi3xx_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ class MI3XXAnalyzer(DataAnalyzer[ServiceabilityDataModel, ServiceabilityAnalyzer

DATA_MODEL = ServiceabilityDataModel

DOCUMENTATION_ANALYSIS_ITEMS: tuple[str, ...] = (
"Builds AFID events from collected Redfish event log members (and optional assembly metadata).",
"Optionally decodes CPER attachments via analysis_args.cper_decode_module before hub analysis.",
"Runs the configured Python service hub (hub_python_module) to produce service recommendations.",
"When analysis_args.skip_hub is true, only builds AFID events without running the hub.",
)

def analyze_data(
self,
data: ServiceabilityDataModel,
Expand Down
8 changes: 8 additions & 0 deletions nodescraper/plugins/serviceability/mi3xx/mi3xx_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ class MI3XXCollector(ServiceabilityCollectorBase[MI3XXCollectorArgs]):
"""Collect MI3XX BMC Redfish data: event log members (with pagination), firmware inventory,
CPER attachment bytes for qualifying events, and optional assembly/chassis metadata."""

DOCUMENTATION_COLLECTION_ITEMS: tuple[str, ...] = (
"Redfish GET: BMC event log Entries (collection_args.rf_event_log_uri; optional uri alias).",
"Paginated Members collection and optional top, reference_time/time_operator filters.",
"Redfish GET: CPER AdditionalDataURI binaries for DiagnosticDataType=CPER events (base64 in data model).",
"Optional chassis Assembly GETs (rf_assembly_uri_template + rf_chassis_devices).",
"Optional firmware bundle inventory GET (rf_firmware_bundle_uri) for component details.",
)

def satisfies_reference_time(
self,
candidate: str,
Expand Down
2 changes: 2 additions & 0 deletions test/unit/plugin/test_mi3xx_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ def test_serviceability_plugin_mi3xx_wiring():
assert ServiceabilityPluginMI3XX.COLLECTOR is MI3XXCollector
assert ServiceabilityPluginMI3XX.COLLECTOR_ARGS is MI3XXCollectorArgs
assert ServiceabilityPluginMI3XX.ANALYZER is MI3XXAnalyzer
assert MI3XXCollector.DOCUMENTATION_COLLECTION_ITEMS
assert MI3XXAnalyzer.DOCUMENTATION_ANALYSIS_ITEMS


def test_mi3xx_collector_no_args(mi3xx_collector):
Expand Down