From f7d686143332e22590b3dac3ec0cb7c82c51c8ce Mon Sep 17 00:00:00 2001 From: elhoim Date: Thu, 24 Sep 2026 11:36:08 +0000 Subject: [PATCH] Check all plugins before installing any in plugin install 'sigma plugin install A B' checked and installed the plugins one by one. If B was incompatible, the command failed after A had already been installed, and the pySigma version check was skipped. All plugins are now resolved and checked for compatibility first; if any is incompatible, all incompatible plugins are reported and nothing is installed. --force-install is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- sigma/cli/plugin.py | 28 ++++++++++++++++++------ tests/test_plugin.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/sigma/cli/plugin.py b/sigma/cli/plugin.py index 6a5e9c6..f643491 100644 --- a/sigma/cli/plugin.py +++ b/sigma/cli/plugin.py @@ -135,15 +135,29 @@ def show_plugin(uuid: bool, plugin_identifier: str): def install_plugin( uuid: bool, compatibility_check: bool, check_pysigma: bool, plugin_identifiers: List[str] ): - for plugin_identifier in plugin_identifiers: - plugin = get_plugin(uuid, plugin_identifier) - if not compatibility_check or plugin.is_compatible(): - plugin.install() - click.echo(f"Successfully installed plugin '{plugin_identifier}'") - else: + # Resolve and check all plugins before installing any of them, so an incompatible plugin + # doesn't leave the plugins given before it installed. + plugins = [ + (plugin_identifier, get_plugin(uuid, plugin_identifier)) + for plugin_identifier in plugin_identifiers + ] + if compatibility_check: + incompatible = [ + plugin_identifier + for plugin_identifier, plugin in plugins + if not plugin.is_compatible() + ] + if incompatible: raise click.exceptions.ClickException( - "Plugin not compatible with installed pySigma version! " + click.style("Use '--force-install' or its shortcut '-f' to install anyway.", fg="green") + "Plugin not compatible with installed pySigma version: " + + ", ".join(f"'{plugin_identifier}'" for plugin_identifier in incompatible) + + "! No plugin was installed. " + + click.style("Use '--force-install' or its shortcut '-f' to install anyway.", fg="green") ) + + for plugin_identifier, plugin in plugins: + plugin.install() + click.echo(f"Successfully installed plugin '{plugin_identifier}'") if check_pysigma: check_pysigma_command() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 19e0912..287da25 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,5 +1,6 @@ from click.testing import CliRunner +import sigma.cli.plugin from sigma.cli.plugin import ( plugin_group, list_plugins, @@ -110,3 +111,53 @@ def test_plugin_uninstall(): result = cli.invoke(uninstall_plugin, ["splunk"]) assert result.exit_code == 0 assert "Successfully uninstalled" in result.output + + +class FakePlugin: + def __init__(self, compatible, installed): + self.compatible = compatible + self.installed = installed + + def is_compatible(self): + return self.compatible + + def install(self): + self.installed.append(self) + + +def _stub_plugins(monkeypatch, compatibility): + installed = [] + plugins = { + identifier: FakePlugin(compatible, installed) + for identifier, compatible in compatibility.items() + } + monkeypatch.setattr( + sigma.cli.plugin, "get_plugin", lambda uuid, identifier: plugins[identifier] + ) + monkeypatch.setattr(sigma.cli.plugin, "check_pysigma_command", lambda: None) + return plugins, installed + + +def test_plugin_install_incompatible_installs_nothing(monkeypatch): + plugins, installed = _stub_plugins(monkeypatch, {"a": True, "b": False}) + cli = CliRunner() + result = cli.invoke(install_plugin, ["a", "b"]) + assert result.exit_code != 0 + assert installed == [] + assert "'b'" in result.output + + +def test_plugin_install_multiple_compatible(monkeypatch): + plugins, installed = _stub_plugins(monkeypatch, {"a": True, "b": True}) + cli = CliRunner() + result = cli.invoke(install_plugin, ["a", "b"]) + assert result.exit_code == 0 + assert installed == [plugins["a"], plugins["b"]] + + +def test_plugin_install_force_incompatible(monkeypatch): + plugins, installed = _stub_plugins(monkeypatch, {"a": True, "b": False}) + cli = CliRunner() + result = cli.invoke(install_plugin, ["-f", "a", "b"]) + assert result.exit_code == 0 + assert installed == [plugins["a"], plugins["b"]]