From 09626faadb129e02d20cd02ea49cf9a8bfcd39bd Mon Sep 17 00:00:00 2001 From: Luka Racic Date: Mon, 14 Oct 2024 22:21:58 +0200 Subject: [PATCH] Migrate agent_smb_share to SSC and ruleset APIs CMK-17402 Change-Id: I21c4ad33b9e9ab2634ede4d00eac7d4077f8ad54 --- cmk/base/legacy_checks/agent_smb_share.py | 37 ----- .../plugins/wato/special_agents/smb_share.py | 93 ------------ cmk/plugins/smb/rulesets/special_agent.py | 141 ++++++++++++++++++ .../smb/server_side_calls/special_agent.py | 53 +++++++ cmk/utils/password_store/hack.py | 1 + tests/unit/checks/test_agent_smb_share.py | 22 --- .../server_side_calls/test_special_agent.py | 66 ++++++++ .../test_special_agent_args.py | 1 - 8 files changed, 261 insertions(+), 153 deletions(-) delete mode 100644 cmk/base/legacy_checks/agent_smb_share.py delete mode 100644 cmk/gui/plugins/wato/special_agents/smb_share.py create mode 100644 cmk/plugins/smb/rulesets/special_agent.py create mode 100644 cmk/plugins/smb/server_side_calls/special_agent.py delete mode 100644 tests/unit/checks/test_agent_smb_share.py create mode 100644 tests/unit/cmk/plugins/smb/server_side_calls/test_special_agent.py diff --git a/cmk/base/legacy_checks/agent_smb_share.py b/cmk/base/legacy_checks/agent_smb_share.py deleted file mode 100644 index 5f85ad0f74a..00000000000 --- a/cmk/base/legacy_checks/agent_smb_share.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - - -from collections.abc import Mapping, Sequence -from typing import Any - -from cmk.base.config import special_agent_info - -from cmk.agent_based.v0_unstable_legacy import passwordstore_get_cmdline - - -def agent_smb_share_arguments( - params: Mapping[str, Any], hostname: str, ipaddress: str | None -) -> Sequence[str]: - default_ipaddress = ipaddress if ipaddress else "" - args = [params.get("hostname", hostname), params.get("ip_address", default_ipaddress)] - - if authentication := params.get("authentication"): - args.append("--username") - args.append(authentication[0]) - args.append("--password") - args.append(passwordstore_get_cmdline("%s", authentication[1])) - - if patterns := params.get("patterns"): - args.append("--patterns") - args.extend(patterns) - - if params.get("recursive", False): - args.append("--recursive") - - return args - - -special_agent_info["smb_share"] = agent_smb_share_arguments diff --git a/cmk/gui/plugins/wato/special_agents/smb_share.py b/cmk/gui/plugins/wato/special_agents/smb_share.py deleted file mode 100644 index 6ef33cdd8ae..00000000000 --- a/cmk/gui/plugins/wato/special_agents/smb_share.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - -from cmk.utils.rulesets.definition import RuleGroup - -from cmk.gui.i18n import _ -from cmk.gui.valuespec import Checkbox, Dictionary, HostAddress, ListOfStrings, TextInput, Tuple -from cmk.gui.wato import MigrateToIndividualOrStoredPassword, RulespecGroupDatasourceProgramsApps -from cmk.gui.watolib.rulespecs import HostRulespec, rulespec_registry - - -def _valuespec_special_agents_smb_share(): - return Dictionary( - elements=[ - ( - "hostname", - TextInput( - title="Host name", - allow_empty=False, - help=_( - "

Usually Checkmk will use the host name of the host it is attached to. " - "With this option you can override this parameter.

" - ), - ), - ), - ( - "ip_address", - HostAddress( - title=_("IP address"), - allow_empty=False, - allow_ipv6_address=False, - help=_( - "

Usually Checkmk will use the primary IP address of the host it is " - "attached to. With this option you can override this parameter.

" - ), - ), - ), - ( - "authentication", - Tuple( - title=_("Authentication"), - elements=[ - TextInput(title=_("Username"), allow_empty=False), - MigrateToIndividualOrStoredPassword(title=_("Password"), allow_empty=False), - ], - ), - ), - ( - "patterns", - ListOfStrings( - title=_("File patterns"), - size=80, - help=_( - "

Here you can specify a list of filename patterns to be sent by the " - "agent in the section fileinfo. UNC paths with globbing patterns " - "are used here, e.g. \\\\hostname\\share name\\*\\foo\\*.log. " - "Wildcards are not allowed in host or share names. " - "Per default each found file will be monitored for size and age. " - "By building groups you can alternatively monitor a collection " - "of files as an entity and monitor the count, total size, the largest, " - "smallest oldest or newest file. Note: if you specify more than one matching rule, then " - "all matching rules will be used for defining pattern - not just the " - " first one.

" - ), - valuespec=TextInput(size=80), - ), - ), - ( - "recursive", - Checkbox( - title=_("Recursive pattern search"), - label=_("Match multiple directories with **"), - help=_( - "If ** is used in the pattern, the agent will recursively look into all the subfolders, " - "so use this carefully on a deeply nested filesystems." - ), - ), - ), - ], - optional_keys=["hostname", "ip_address", "authentication", "recursive"], - title=_("SMB Share fileinfo"), - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupDatasourceProgramsApps, - name=RuleGroup.SpecialAgents("smb_share"), - valuespec=_valuespec_special_agents_smb_share, - ) -) diff --git a/cmk/plugins/smb/rulesets/special_agent.py b/cmk/plugins/smb/rulesets/special_agent.py new file mode 100644 index 00000000000..8ea20502719 --- /dev/null +++ b/cmk/plugins/smb/rulesets/special_agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. + +from collections.abc import Mapping + +from cmk.utils.hostaddress import HostAddress + +from cmk.rulesets.v1 import Help, Label, Message, Title +from cmk.rulesets.v1.form_specs import ( + BooleanChoice, + DictElement, + Dictionary, + FieldSize, + List, + migrate_to_password, + Password, + String, + validators, +) +from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + title=Title("SMB Share fileinfo"), + elements={ + "hostname": DictElement( + required=False, + parameter_form=String( + title=Title("Host name"), + custom_validate=(validators.LengthInRange(min_value=1),), + help_text=Help( + "

Usually Checkmk will use the host name of the host it is attached to. " + "With this option you can override this parameter.

" + ), + ), + ), + "ip_address": DictElement( + required=False, + parameter_form=String( + title=Title("IP address"), + custom_validate=( + validators.LengthInRange(min_value=1), + _validate_hostname, + ), + help_text=Help( + "

Usually Checkmk will use the primary IP address of the host it is " + "attached to. With this option you can override this parameter.

" + ), + ), + ), + "authentication": DictElement( + required=False, + parameter_form=Dictionary( + title=Title("Authentication"), + elements={ + "username": DictElement( + required=True, + parameter_form=String( + title=Title("Username"), + custom_validate=(validators.LengthInRange(min_value=1),), + ), + ), + "password": DictElement( + required=True, + parameter_form=Password( + title=Title("Password"), + custom_validate=(validators.LengthInRange(min_value=1),), + migrate=migrate_to_password, + ), + ), + }, + migrate=_migrate_tuple_to_dict, + ), + ), + "patterns": DictElement( + required=True, + parameter_form=List( + title=Title("File patterns"), + help_text=Help( + "

Here you can specify a list of filename patterns to be sent by the " + "agent in the section fileinfo. UNC paths with globbing patterns " + "are used here, e.g. \\\\hostname\\share name\\*\\foo\\*.log. " + "Wildcards are not allowed in host or share names. " + "Per default each found file will be monitored for size and age. " + "By building groups you can alternatively monitor a collection " + "of files as an entity and monitor the count, total size, the largest, " + "smallest oldest or newest file. Note: if you specify more than one matching rule, then " + "all matching rules will be used for defining pattern - not just the " + " first one.

" + ), + element_template=String(field_size=FieldSize.LARGE), + add_element_label=Label("Add pattern"), + remove_element_label=Label("Remove pattern"), + editable_order=False, + ), + ), + "recursive": DictElement( + required=False, + parameter_form=BooleanChoice( + title=Title("Recursive pattern search"), + label=Label("Match multiple directories with **"), + help_text=Help( + "If ** is used in the pattern, the agent will recursively look into all the subfolders, " + "so use this carefully on a deeply nested filesystems." + ), + ), + ), + }, + ) + + +def _validate_hostname(value: str) -> None: + try: + HostAddress(value) + except ValueError as exception: + raise validators.ValidationError( + message=Message( + "Please enter a valid host name or IPv4 address. " + "Only letters, digits, dash, underscore and dot are allowed." + ) + ) from exception + + +def _migrate_tuple_to_dict(param: object) -> Mapping[str, object]: + match param: + case (username, password): + return {"username": username, "password": password} + case dict() as already_migrated: + return already_migrated + raise ValueError(param) + + +rule_spec_special_agent_smb_share = SpecialAgent( + name="smb_share", + title=Title("SMB Share fileinfo"), + topic=Topic.GENERAL, + parameter_form=_parameter_form, +) diff --git a/cmk/plugins/smb/server_side_calls/special_agent.py b/cmk/plugins/smb/server_side_calls/special_agent.py new file mode 100644 index 00000000000..f7a463d00ac --- /dev/null +++ b/cmk/plugins/smb/server_side_calls/special_agent.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. + + +from collections.abc import Iterable, Mapping, Sequence + +from pydantic import BaseModel + +from cmk.server_side_calls.v1 import HostConfig, SpecialAgentCommand, SpecialAgentConfig +from cmk.server_side_calls.v1._utils import Secret + + +class Params(BaseModel): + hostname: str | None = None + ip_address: str | None = None + authentication: Mapping[str, str | Secret] | None = None + patterns: Sequence[str] + recursive: bool | None = None + + +def command_function(params: Params, host_config: HostConfig) -> Iterable[SpecialAgentCommand]: + default_ipaddress = ( + params.ip_address if params.ip_address else host_config.primary_ip_config.address + ) + command_arguments: list[str | Secret] = [ + params.hostname if params.hostname else host_config.name, + params.ip_address if params.ip_address else default_ipaddress, + ] + + if params.authentication: + command_arguments.append("--username") + command_arguments.append(params.authentication["username"]) + command_arguments.append("--password") + assert isinstance(password := params.authentication["password"], Secret) + command_arguments.append(password.unsafe()) + + if params.patterns: + command_arguments.append("--patterns") + command_arguments.extend(params.patterns) + + if params.recursive: + command_arguments.append("--recursive") + + yield SpecialAgentCommand(command_arguments=command_arguments) + + +special_agent_smb_share = SpecialAgentConfig( + name="smb_share", + parameter_parser=Params.model_validate, + commands_function=command_function, +) diff --git a/cmk/utils/password_store/hack.py b/cmk/utils/password_store/hack.py index ea844b89479..00b0100cb75 100644 --- a/cmk/utils/password_store/hack.py +++ b/cmk/utils/password_store/hack.py @@ -71,6 +71,7 @@ "splunk": True, "vnx_quotas": True, "ucs_bladecenter": True, + "smb_share": True, } diff --git a/tests/unit/checks/test_agent_smb_share.py b/tests/unit/checks/test_agent_smb_share.py deleted file mode 100644 index 0c12df38c9f..00000000000 --- a/tests/unit/checks/test_agent_smb_share.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - -from .checktestlib import SpecialAgent - - -def test_agent_smb_share_arguments_password_store() -> None: - params = { - "authentication": ("user", ("password", "passwd")), - "patterns": [], - } - agent = SpecialAgent("agent_smb_share") - assert agent.argument_func(params, "testhost", "1.2.3.4") == [ - "testhost", - "1.2.3.4", - "--username", - "user", - "--password", - "passwd", - ] diff --git a/tests/unit/cmk/plugins/smb/server_side_calls/test_special_agent.py b/tests/unit/cmk/plugins/smb/server_side_calls/test_special_agent.py new file mode 100644 index 00000000000..e3af853d04e --- /dev/null +++ b/tests/unit/cmk/plugins/smb/server_side_calls/test_special_agent.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. + +from collections.abc import Mapping + +import pytest + +from cmk.plugins.smb.server_side_calls.special_agent import special_agent_smb_share +from cmk.server_side_calls.v1 import HostConfig, IPv4Config, SpecialAgentCommand +from cmk.server_side_calls.v1._utils import Secret + +HOST_CONFIG = HostConfig( + name="testhost", + ipv4_config=IPv4Config(address="1.2.3.4"), +) + + +@pytest.mark.parametrize( + "raw_params, expected_command", + [ + pytest.param( + { + "authentication": {"username": "user", "password": Secret(23)}, + "patterns": [], + }, + SpecialAgentCommand( + command_arguments=[ + "testhost", + "1.2.3.4", + "--username", + "user", + "--password", + Secret(23).unsafe(), + ] + ), + id="explicit_password_no_ip", + ), + pytest.param( + { + "authentication": {"username": "user", "password": Secret(23)}, + "ip_address": "2.3.4", + "patterns": [], + "recursive": True, + }, + SpecialAgentCommand( + command_arguments=[ + "testhost", + "2.3.4", + "--username", + "user", + "--password", + Secret(23).unsafe(), + "--recursive", + ] + ), + id="explicit_password_with_ip", + ), + ], +) +def test_special_agent_smb_share_command_creation( + raw_params: Mapping[str, object], + expected_command: SpecialAgentCommand, +) -> None: + assert list(special_agent_smb_share(raw_params, HOST_CONFIG)) == [expected_command] diff --git a/tests/unit/cmk/plugins_consistency/test_special_agent_args.py b/tests/unit/cmk/plugins_consistency/test_special_agent_args.py index 30d14c85920..3f4cbf489f0 100644 --- a/tests/unit/cmk/plugins_consistency/test_special_agent_args.py +++ b/tests/unit/cmk/plugins_consistency/test_special_agent_args.py @@ -109,7 +109,6 @@ "jira", "ruckus_spot", "salesforce", - "smb_share", }