Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix logging of incompatible plugins #395

Merged
merged 8 commits into from
Sep 14, 2023
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
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,
Schamper marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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

Check warning on line 159 in dissect/target/tools/query.py

View check run for this annotation

Codecov / codecov/patch

dissect/target/tools/query.py#L159

Added line #L159 was not covered by tests
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 @@
)
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