Skip to content

Commit

Permalink
Fix logging of incompatible plugins (#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schamper committed Sep 14, 2023
1 parent e5da4e4 commit ea2aa14
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 20 deletions.
7 changes: 5 additions & 2 deletions dissect/target/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down
8 changes: 3 additions & 5 deletions dissect/target/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 5 additions & 5 deletions dissect/target/tools/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 24 additions & 4 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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])(
Expand Down Expand Up @@ -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(
Expand All @@ -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)
26 changes: 22 additions & 4 deletions tests/test_tools_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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

Expand All @@ -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

0 comments on commit ea2aa14

Please sign in to comment.