diff --git a/dissect/target/plugin.py b/dissect/target/plugin.py index f02b6171d..fd9fc08b0 100644 --- a/dissect/target/plugin.py +++ b/dissect/target/plugin.py @@ -955,6 +955,7 @@ def __init_subclass__(cls, **kwargs): @dataclass(frozen=True, eq=True) class PluginFunction: name: str + path: str output_type: str class_object: type[Plugin] method_name: str @@ -1074,7 +1075,8 @@ def add_to_result(func: PluginFunction) -> None: matches = True add_to_result( PluginFunction( - name=index_name, + name=f"{func['namespace']}.{method_name}" if func["namespace"] else method_name, + path=index_name, class_object=loaded_plugin_object, method_name=method_name, output_type=getattr(fobject, "__output__", "text"), @@ -1112,7 +1114,8 @@ def add_to_result(func: PluginFunction) -> None: add_to_result( PluginFunction( - name=f"{description['module']}.{pattern}", + name=f"{description['namespace']}.{funcname}" if description["namespace"] else funcname, + path=f"{description['module']}.{funcname}", class_object=loaded_plugin_object, method_name=funcname, output_type=getattr(fobject, "__output__", "text"), diff --git a/dissect/target/target.py b/dissect/target/target.py index d1144dbb7..fccf5f849 100644 --- a/dissect/target/target.py +++ b/dissect/target/target.py @@ -329,8 +329,7 @@ def _load_child_plugins(self) -> None: continue try: - if not child_plugin.is_compatible(): - continue + child_plugin.check_compatible() self._child_plugins[child_plugin.__type__] = child_plugin except PluginError as e: self.log.info("Child plugin reported itself as incompatible: %s (%s)", plugin_desc["class"], e) @@ -517,10 +516,9 @@ def add_plugin( if check_compatible: try: - if not p.is_compatible(): - self.send_event(Event.INCOMPATIBLE_PLUGIN, plugin_cls=plugin_cls) - raise UnsupportedPluginError(f"Plugin reported itself as incompatible: {plugin_cls}") + p.check_compatible() except PluginError: + self.send_event(Event.INCOMPATIBLE_PLUGIN, plugin_cls=plugin_cls) raise except Exception as e: raise UnsupportedPluginError( diff --git a/dissect/target/tools/query.py b/dissect/target/tools/query.py index 03a0fcc80..caeb6f8e3 100644 --- a/dissect/target/tools/query.py +++ b/dissect/target/tools/query.py @@ -156,11 +156,11 @@ def main(): parser.error("can't list compatible plugins for remote targets.") funcs, _ = find_plugin_functions(plugin_target, args.list, True, show_hidden=True) for func in funcs: - collected_plugins[func.name] = func.plugin_desc + collected_plugins[func.path] = func.plugin_desc else: funcs, _ = find_plugin_functions(Target(), args.list, False, show_hidden=True) for func in funcs: - collected_plugins[func.name] = func.plugin_desc + collected_plugins[func.path] = func.plugin_desc # Display in a user friendly manner target = Target() @@ -256,12 +256,12 @@ def main(): ) except UnsupportedPluginError as e: target.log.error( - "Unsupported plugin for `%s`: %s", - func_def, + "Unsupported plugin for %s: %s", + func_def.name, e.root_cause_str(), ) - target.log.debug("", exc_info=e) + target.log.debug("%s", func_def, exc_info=e) continue except PluginNotFoundError: target.log.error("Cannot find plugin `%s`", func_def) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index ea8d4b359..19faa1617 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -5,11 +5,13 @@ import pytest +from dissect.target.exceptions import UnsupportedPluginError from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension from dissect.target.helpers.record import create_extended_descriptor from dissect.target.plugin import ( PLUGINS, NamespacePlugin, + Plugin, environment_variable_paths, export, find_plugin_functions, @@ -118,14 +120,16 @@ def test_find_plugin_function_windows(target_win: Target) -> None: found, _ = find_plugin_functions(target_win, "services") assert len(found) == 1 - assert found[0].name == "os.windows.services.services" + assert found[0].name == "services" + assert found[0].path == "os.windows.services.services" def test_find_plugin_function_unix(target_unix: Target) -> None: found, _ = find_plugin_functions(target_unix, "services") assert len(found) == 1 - assert found[0].name == "os.unix.services.services" + assert found[0].name == "services" + assert found[0].path == "os.unix.services.services" TestRecord = create_extended_descriptor([UserRecordDescriptorExtension])( @@ -175,8 +179,14 @@ def test_find_plugin_function_default(target_default: Target) -> None: assert len(found) == 2 names = [item.name for item in found] - assert "os.unix.services.services" in names - assert "os.windows.services.services" in names + assert "services" in names + assert "services" in names + paths = [item.path for item in found] + assert "os.unix.services.services" in paths + assert "os.windows.services.services" in paths + + found, _ = find_plugin_functions(target_default, "mcafee.msc") + assert found[0].path == "apps.av.mcafee.msc" @pytest.mark.parametrize( @@ -192,3 +202,13 @@ def test_find_plugin_function_default(target_default: Target) -> None: def test_find_plugin_function_order(target_win: Target, pattern: str) -> None: found = ",".join(reduce(lambda rs, el: rs + [el.method_name], find_plugin_functions(target_win, pattern)[0], [])) assert found == pattern + + +class _TestIncompatiblePlugin(Plugin): + def check_compatible(self): + raise UnsupportedPluginError("My incompatible plugin error") + + +def test_incompatible_plugin(mock_target: Target) -> None: + with pytest.raises(UnsupportedPluginError, match="My incompatible plugin error"): + mock_target.add_plugin(_TestIncompatiblePlugin) diff --git a/tests/test_tools_query.py b/tests/test_tools_query.py index 8c9390263..35f8a6c14 100644 --- a/tests/test_tools_query.py +++ b/tests/test_tools_query.py @@ -5,13 +5,13 @@ from dissect.target.tools.query import main as target_query -def test_target_query_list(capsys, monkeypatch): +def test_target_query_list(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None: with monkeypatch.context() as m: m.setattr("sys.argv", ["target-query", "--list"]) with pytest.raises((SystemExit, IndexError, ImportError)): target_query() - out, err = capsys.readouterr() + out, _ = capsys.readouterr() assert out.startswith("Available plugins:") assert "Failed to load:\n None\nAvailable loaders:\n" in out @@ -42,7 +42,12 @@ def test_target_query_list(capsys, monkeypatch): ), ], ) -def test_target_query_invalid_functions(capsys, monkeypatch, given_funcs, expected_invalid_funcs): +def test_target_query_invalid_functions( + capsys: pytest.CaptureFixture, + monkeypatch: pytest.MonkeyPatch, + given_funcs: list[str], + expected_invalid_funcs: list[str], +) -> None: with monkeypatch.context() as m: m.setattr( "sys.argv", @@ -51,7 +56,7 @@ def test_target_query_invalid_functions(capsys, monkeypatch, given_funcs, expect with pytest.raises((SystemExit)): target_query() - out, err = capsys.readouterr() + _, err = capsys.readouterr() assert "target-query: error: argument -f/--function contains invalid plugin(s):" in err @@ -63,3 +68,16 @@ def test_target_query_invalid_functions(capsys, monkeypatch, given_funcs, expect invalid_funcs.sort() assert invalid_funcs == expected_invalid_funcs + + +def test_target_query_unsupported_plugin_log(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None: + with monkeypatch.context() as m: + m.setattr( + "sys.argv", + ["target-query", "-f", "regf", "tests/data/loaders/tar/test-archive-dot-folder.tgz"], + ) + + target_query() + _, err = capsys.readouterr() + + assert "Unsupported plugin for regf: Registry plugin not loaded" in err