Skip to content

Commit 46fb227

Browse files
committed
Merge branch 'development' into alex_collector_args
2 parents a3ceda5 + a498e7c commit 46fb227

File tree

5 files changed

+98
-60
lines changed

5 files changed

+98
-60
lines changed

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ The Node Scraper CLI can be used to run Node Scraper plugins on a target system.
1717
options are available:
1818

1919
```sh
20-
usage: node-scraper [-h] [--sys-name STRING] [--sys-location {LOCAL,REMOTE}]
21-
[--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}] [--sys-sku STRING] [--sys-platform STRING]
22-
[--plugin-configs [STRING ...]] [--system-config STRING] [--connection-config STRING] [--log-path STRING]
23-
[--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}] [--gen-reference-config]
20+
usage: node-scraper [-h] [--sys-name STRING] [--sys-location {LOCAL,REMOTE}] [--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}]
21+
[--sys-sku STRING] [--sys-platform STRING] [--plugin-configs [STRING ...]] [--system-config STRING]
22+
[--connection-config STRING] [--log-path STRING] [--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
23+
[--gen-reference-config]
2424
{run-plugins,describe,gen-plugin-config} ...
2525

2626
node scraper CLI
@@ -38,14 +38,12 @@ options:
3838
--sys-location {LOCAL,REMOTE}
3939
Location of target system (default: LOCAL)
4040
--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}
41-
Specify system interaction level, used to determine the type of actions that plugins can perform (default:
42-
INTERACTIVE)
41+
Specify system interaction level, used to determine the type of actions that plugins can perform (default: INTERACTIVE)
4342
--sys-sku STRING Manually specify SKU of system (default: None)
4443
--sys-platform STRING
4544
Specify system platform (default: None)
4645
--plugin-configs [STRING ...]
47-
built-in config names or paths to plugin config JSONs. Available built-in configs: NodeStatus (default:
48-
None)
46+
built-in config names or paths to plugin config JSONs. Available built-in configs: NodeStatus (default: None)
4947
--system-config STRING
5048
Path to system config json (default: None)
5149
--connection-config STRING
@@ -54,7 +52,8 @@ options:
5452
--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}
5553
Change python log level (default: INFO)
5654
--gen-reference-config
57-
Generate reference config. File will be written to ./reference_config.json. (default: False)
55+
Generate reference config from system. Writes to ./reference_config.json. (default: False)
56+
5857

5958
```
6059
@@ -254,8 +253,8 @@ Here is an example of a comprehensive plugin config that specifies analyzer args
254253
```
255254
256255
2. **'gen-reference-config' command**
257-
This command can be used generate a reference config that is populated with current system
258-
configurations. The plugins that use analyzer args, where applied, will be populated with system
256+
This command can be used to generate a reference config that is populated with current system
257+
configurations. Plugins that use analyzer args (where applicable) will be populated with system
259258
data.
260259
Sample command:
261260
```sh
@@ -286,8 +285,16 @@ This will generate the following config:
286285
},
287286
"result_collators": {}
288287
```
289-
This can be later used on a different platform for comparison, using the steps at #2:
288+
This config can later be used on a different platform for comparison, using the steps at #2:
290289
```sh
291290
node-scraper --plugin-configs reference_config.json
292291

293292
```
293+
294+
An alternate way to generate a reference config is by using log files from a previous run. The
295+
example below uses log files from 'scraper_logs_<path>/':
296+
```sh
297+
node-scraper gen-plugin-config --gen-reference-config-from-logs scraper_logs_<path>/ --output-path custom_output_dir
298+
```
299+
This will generate a reference config that includes plugins with logged results in
300+
'scraper_log_<path>' and save the new config to 'custom_output_dir/reference_config.json'.

nodescraper/cli/cli.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,6 @@ def build_parser(
152152
help="Generate reference config from system. Writes to ./reference_config.json.",
153153
)
154154

155-
parser.add_argument(
156-
"--gen-reference-config-from-logs",
157-
dest="reference_config_from_logs",
158-
type=log_path_arg,
159-
help="Generate reference config from previous run logfiles. Writes to ./reference_config.json.",
160-
)
161-
162155
subparsers = parser.add_subparsers(dest="subcmd", help="Subcommands")
163156

164157
run_plugin_parser = subparsers.add_parser(
@@ -188,6 +181,13 @@ def build_parser(
188181
help="Generate a config for a plugin or list of plugins",
189182
)
190183

184+
config_builder_parser.add_argument(
185+
"--gen-reference-config-from-logs",
186+
dest="reference_config_from_logs",
187+
type=log_path_arg,
188+
help="Generate reference config from previous run logfiles. Writes to --output-path/reference_config.json if provided, otherwise ./reference_config.json.",
189+
)
190+
191191
config_builder_parser.add_argument(
192192
"--plugins",
193193
nargs="*",
@@ -346,24 +346,28 @@ def main(arg_input: Optional[list[str]] = None):
346346
parse_describe(parsed_args, plugin_reg, config_reg, logger)
347347

348348
if parsed_args.subcmd == "gen-plugin-config":
349-
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
350349

351-
if parsed_args.reference_config_from_logs:
352-
ref_config = generate_reference_config_from_logs(
353-
parsed_args.reference_config_from_logs, plugin_reg, logger
354-
)
355-
path = os.path.join(os.getcwd(), "reference_config.json")
356-
try:
357-
with open(path, "w") as f:
358-
json.dump(
359-
ref_config.model_dump(mode="json", exclude_none=True),
360-
f,
361-
indent=2,
362-
)
363-
logger.info("Reference config written to: %s", path)
364-
except Exception as exp:
365-
logger.error(exp)
366-
sys.exit(0)
350+
if parsed_args.reference_config_from_logs:
351+
ref_config = generate_reference_config_from_logs(
352+
parsed_args.reference_config_from_logs, plugin_reg, logger
353+
)
354+
output_path = os.getcwd()
355+
if parsed_args.output_path:
356+
output_path = parsed_args.output_path
357+
path = os.path.join(output_path, "reference_config.json")
358+
try:
359+
with open(path, "w") as f:
360+
json.dump(
361+
ref_config.model_dump(mode="json", exclude_none=True),
362+
f,
363+
indent=2,
364+
)
365+
logger.info("Reference config written to: %s", path)
366+
except Exception as exp:
367+
logger.error(exp)
368+
sys.exit(0)
369+
370+
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
367371

368372
parsed_plugin_args = {}
369373
for plugin, plugin_args in plugin_arg_map.items():

nodescraper/cli/helper.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from pathlib import Path
3232
from typing import Optional, Tuple
3333

34+
from pydantic import BaseModel
35+
3436
from nodescraper.cli.inputargtypes import ModelArgHandler
3537
from nodescraper.configbuilder import ConfigBuilder
3638
from nodescraper.configregistry import ConfigRegistry
@@ -284,6 +286,33 @@ def log_system_info(log_path: str | None, system_info: SystemInfo, logger: loggi
284286
logger.error(exp)
285287

286288

289+
def extract_analyzer_args_from_model(
290+
plugin_cls: type, data_model: BaseModel, logger: logging.Logger
291+
) -> Optional[BaseModel]:
292+
"""Extract analyzer args from a plugin and a data model.
293+
294+
Args:
295+
plugin_cls (type): The plugin class from registry.
296+
data_model (BaseModel): System data model.
297+
logger (logging.Logger): logger.
298+
299+
Returns:
300+
Optional[BaseModel]: Instance of analyzer args model or None if unavailable.
301+
"""
302+
if not hasattr(plugin_cls, "ANALYZER_ARGS") or not plugin_cls.ANALYZER_ARGS:
303+
logger.warning(
304+
"Plugin: %s does not support reference config creation. No analyzer args defined.",
305+
getattr(plugin_cls, "__name__", str(plugin_cls)),
306+
)
307+
return None
308+
309+
try:
310+
return plugin_cls.ANALYZER_ARGS.build_from_model(data_model)
311+
except NotImplementedError as e:
312+
logger.info("%s: %s", plugin_cls.__name__, str(e))
313+
return None
314+
315+
287316
def generate_reference_config(
288317
results: list[PluginResult], plugin_reg: PluginRegistry, logger: logging.Logger
289318
) -> PluginConfig:
@@ -314,18 +343,9 @@ def generate_reference_config(
314343
continue
315344

316345
plugin = plugin_reg.plugins.get(obj.source)
317-
if not plugin.ANALYZER_ARGS:
318-
logger.warning(
319-
"Plugin: %s does not support reference config creation. No analyzer args defined, skipping.",
320-
obj.source,
321-
)
322-
continue
323346

324-
args = None
325-
try:
326-
args = plugin.ANALYZER_ARGS.build_from_model(data_model)
327-
except NotImplementedError as nperr:
328-
logger.info(nperr)
347+
args = extract_analyzer_args_from_model(plugin, data_model, logger)
348+
if not args:
329349
continue
330350
plugins[obj.source] = {"analysis_args": {}}
331351
plugins[obj.source]["analysis_args"] = args.model_dump(exclude_none=True)
@@ -357,24 +377,20 @@ def generate_reference_config_from_logs(
357377
dm_path = Path(dm)
358378
dm_payload = json.loads(dm_path.read_text(encoding="utf-8"))
359379
plugin = plugin_reg.plugins.get(task_res.parent)
360-
data_model = plugin.DATA_MODEL.model_validate(dm_payload)
361-
362-
if not plugin.ANALYZER_ARGS:
380+
if not plugin:
363381
logger.warning(
364-
"Plugin: %s does not support reference config creation. No analyzer args defined.",
382+
"Plugin %s not found in the plugin registry: %s.",
365383
task_res.parent,
366384
)
367385
continue
368386

369-
args = None
387+
data_model = plugin.DATA_MODEL.model_validate(dm_payload)
370388

371-
try:
372-
args = plugin.ANALYZER_ARGS.build_from_model(data_model)
373-
except NotImplementedError as nperr:
374-
logger.info(nperr)
389+
args = extract_analyzer_args_from_model(plugin, data_model, logger)
390+
if not args:
375391
continue
376-
plugins[task_res.parent] = {"analysis_args": {}}
377-
plugins[task_res.parent]["analysis_args"] = args.model_dump(exclude_none=True)
392+
393+
plugins[task_res.parent] = {"analysis_args": args.model_dump(exclude_none=True)}
378394

379395
plugin_config.plugins = plugins
380396
return plugin_config

nodescraper/models/taskresult.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
###############################################################################
2626
import datetime
2727
import logging
28-
from typing import Optional
28+
from typing import Any, Optional
2929

3030
from pydantic import BaseModel, Field, field_serializer, field_validator
3131

@@ -67,7 +67,18 @@ def serialize_status(self, status: ExecutionStatus, _info) -> str:
6767

6868
@field_validator("status", mode="before")
6969
@classmethod
70-
def _coerce_status(cls, v):
70+
def validate_status(cls, v: Any):
71+
"""Validator to ensure `status` is a valid ExecutionStatus enum.
72+
73+
Args:
74+
v (Any): The input value to validate (can be str or ExecutionStatus).
75+
76+
Returns:
77+
ExecutionStatus: The validated enum value.
78+
79+
Raises:
80+
ValueError: If the string is not a valid enum name.
81+
"""
7182
if isinstance(v, ExecutionStatus):
7283
return v
7384
if isinstance(v, str):

0 commit comments

Comments
 (0)