diff --git a/sigma/cli/plugin.py b/sigma/cli/plugin.py index 6a5e9c6..2bb819b 100644 --- a/sigma/cli/plugin.py +++ b/sigma/cli/plugin.py @@ -155,19 +155,34 @@ def install_plugin( default=True, help="Enable or disable plugin compatibility check.", ) -def upgrade_plugin(compatibility_check: bool): +@click.option( + "--check-pysigma/--no-check-pysigma", + "-l/-L", + default=True, + help="Check after plugin upgrade if pySigma version is still matching the CLI requirement.", +) +def upgrade_plugin(compatibility_check: bool, check_pysigma: bool): plugins_dir = SigmaPluginDirectory.default_plugin_directory() for plugin_id in plugins_dir.plugins: plugin = plugins_dir.get_plugin_by_uuid(uuid=plugin_id) if plugin.is_installed(): - if not compatibility_check or plugin.is_compatible(): + if not compatibility_check: plugin.upgrade() click.echo(f"Successfully upgrade plugin '{plugin.id}'") + elif plugin.is_compatible(): + # Install the newest release that is compatible with the installed pySigma version + # instead of an unpinned upgrade to the newest release, which might require + # another pySigma version. + plugin.install() + click.echo(f"Successfully upgrade plugin '{plugin.id}'") else: click.echo( f"Plugin '{plugin.id}' not compatible with installed pySigma version" ) + if check_pysigma: + check_pysigma_command() + @plugin_group.command(name="uninstall", help="Uninstall plugin by identifier or UUID.") @click.option("--uuid", "-u", is_flag=True, help="Uninstall plugin by UUID.") diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 19e0912..286b857 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,11 +1,16 @@ +import pytest from click.testing import CliRunner +import sigma.cli.plugin +import sigma.plugins from sigma.cli.plugin import ( plugin_group, list_plugins, install_plugin, uninstall_plugin, + upgrade_plugin, ) +from sigma.plugins import SigmaPlugin, SigmaPluginDirectory def test_plugin_help(): @@ -110,3 +115,71 @@ def test_plugin_uninstall(): result = cli.invoke(uninstall_plugin, ["splunk"]) assert result.exit_code == 0 assert "Successfully uninstalled" in result.output + + +@pytest.fixture +def stubbed_plugin(monkeypatch): + """ + An installed plugin with two releases on (stubbed) PyPI: 1.0.0 is compatible with the installed + pySigma 1.x, 2.0.0 requires pySigma 2.x. pip calls and the pySigma check are recorded. + """ + requirements = { + "1.0.0": ["pysigma (>=1.0.0,<2.0.0)"], + "2.0.0": ["pysigma (>=2.0.0,<3.0.0)"], + } + + def fake_pypi_json(package, version=None): + if version is None: + return {"releases": {v: [{"file": 1}] for v in requirements}} + return {"info": {"requires_dist": requirements[version]}} + + plugin = SigmaPlugin.from_dict( + { + "uuid": "00000000-0000-4000-8000-000000000001", + "type": "backend", + "id": "demo", + "description": "demo", + "package": "pysigma-backend-demo", + "project-url": "https://example.invalid", + "report-issue-url": "https://example.invalid", + "state": "stable", + "pysigma-version": ">=0.1", + } + ) + directory = SigmaPluginDirectory() + directory.register_plugin(plugin) + + calls = {"pip": [], "check_pysigma": 0} + + def fake_check_call(cmd, *args, **kwargs): + calls["pip"].append(cmd[cmd.index("install") + 1 :]) + + def fake_check_pysigma(): + calls["check_pysigma"] += 1 + + monkeypatch.setattr(sigma.plugins.SigmaPlugin, "_get_pypi_json", staticmethod(fake_pypi_json)) + monkeypatch.setattr(sigma.plugins.SigmaPlugin, "is_installed", lambda self: True) + monkeypatch.setattr(sigma.plugins.subprocess, "check_call", fake_check_call) + monkeypatch.setattr( + sigma.cli.plugin.SigmaPluginDirectory, "default_plugin_directory", lambda *a, **k: directory + ) + monkeypatch.setattr(sigma.cli.plugin, "check_pysigma_command", fake_check_pysigma) + return calls + + +def test_plugin_upgrade_installs_compatible_release(stubbed_plugin): + cli = CliRunner() + result = cli.invoke(upgrade_plugin, []) + assert result.exit_code == 0 + assert "Successfully upgrade plugin 'demo'" in result.output + assert stubbed_plugin["pip"][-1][-1] == "pysigma-backend-demo==1.0.0" + assert stubbed_plugin["check_pysigma"] == 1 + + +def test_plugin_upgrade_without_compatibility_check(stubbed_plugin): + cli = CliRunner() + result = cli.invoke(upgrade_plugin, ["--no-compatibility-check", "--no-check-pysigma"]) + assert result.exit_code == 0 + assert "--upgrade" in stubbed_plugin["pip"][-1] + assert stubbed_plugin["pip"][-1][-1] == "pysigma-backend-demo" + assert stubbed_plugin["check_pysigma"] == 0