diff --git a/.werks/15517.md b/.werks/15517.md new file mode 100644 index 00000000000..4cb32f8c1ad --- /dev/null +++ b/.werks/15517.md @@ -0,0 +1,20 @@ +[//]: # (werk v2) +# check_cert: Fixed metrics in output + +key | value +---------- | --- +date | 2024-09-23T14:48:43+00:00 +version | 2.4.0b1 +class | fix +edition | cre +component | checks +level | 2 +compatible | yes + +The active check for monitoring certificates produces two metrics by now. +These metrics have been written in a broken format and therefore never +been created. This is now fixed and both metrics are now available. + +The affected metrics are +* certificate_remaining_validity (is also used for Perf-O-Meter) +* overall_execution_time diff --git a/.werks/16218.md b/.werks/16218.md index c440a5eb7b2..5b5015333d6 100644 --- a/.werks/16218.md +++ b/.werks/16218.md @@ -22,4 +22,4 @@ This issue was found during internal review. *Vulnerability Management*: -We have rated the issue with a CVSS Score of 9.2 High (`CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`) and assigned `CVE-2024-8606`. +We have rated the issue with a CVSS Score of 9.2 Critical (`CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`) and assigned `CVE-2024-8606`. diff --git a/.werks/16251.md b/.werks/16251.md deleted file mode 100644 index fd2afb3d182..00000000000 --- a/.werks/16251.md +++ /dev/null @@ -1,14 +0,0 @@ -[//]: # (werk v2) -# Update monitoring-plugins to 2.4.0 - -key | value ----------- | --- -date | 2024-09-04T14:11:06+00:00 -version | 2.4.0b1 -class | feature -edition | cre -component | checks -level | 1 -compatible | yes - - diff --git a/.werks/17036.md b/.werks/17036.md new file mode 100644 index 00000000000..d193e06587b --- /dev/null +++ b/.werks/17036.md @@ -0,0 +1,17 @@ +[//]: # (werk v2) +# heartbeat_crm: Handle cases when pacemaker service is not running + +key | value +---------- | --- +date | 2024-09-16T21:35:09+00:00 +version | 2.4.0b1 +class | fix +edition | cre +component | checks +level | 1 +compatible | yes + +The creation of the agent section depended on the pacemaker service being running. If this was not the case, the section was left empty, causing the services to become stale. +From now on, if the pacemaker service is not running, the service will go into CRIT state and the summary will indicate that the connection was not possible/refused. + +Also, the agent now checks for the existence of 'crm_mon' on the system, as this is a prerequisite for further command execution. diff --git a/.werks/17134.md b/.werks/17134.md index caa095ba029..3761f7726d3 100644 --- a/.werks/17134.md +++ b/.werks/17134.md @@ -11,4 +11,6 @@ component | inv level | 1 compatible | yes - +Empty nodes, ie. nodes with no attributes or table rows, may be created via inventory plugins or +update actions by means of retention interval configurations. In this case the seemingly changed +trees are not allowed to be saved or archived. diff --git a/.werks/17145.md b/.werks/17145.md new file mode 100644 index 00000000000..b0ae46e06f3 --- /dev/null +++ b/.werks/17145.md @@ -0,0 +1,30 @@ +[//]: # (werk v2) +# Information leak in mknotifyd + +key | value +---------- | --- +date | 2024-07-15T11:23:40+00:00 +version | 2.4.0b1 +class | security +edition | cee +component | notifications +level | 1 +compatible | yes + +When a notification context is sent to mknotifyd a "result message" is generated by mknotifyd and sent back so the original site so it can show if there were problems handling that notification. +This result message could contain secrets that were not meant to be sent to remote sites, e.g. passwords/secrets. + +These secrets were not processed by the remote site but a rough site would have been able to retrieve these. + +This issue was found during internal review. + +*Affected Versions*: + +* 2.3.0 +* 2.2.0 +* 2.1.0 +* 2.0.0 (EOL) + +*Vulnerability Management*: + +We have rated the issue with a CVSS Score of 5.3 Medium (`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N`) and assigned `CVE-2024-6747`. diff --git a/.werks/17285.md b/.werks/17285.md new file mode 100644 index 00000000000..91ff00f4245 --- /dev/null +++ b/.werks/17285.md @@ -0,0 +1,17 @@ +[//]: # (werk v2) +# bonding: Configurable number of expected interfaces + +key | value +---------- | --- +date | 2024-09-16T08:45:31+00:00 +version | 2.4.0b1 +class | feature +edition | cre +component | checks +level | 1 +compatible | yes + + +The number of expected interfaces for the bonding checks can now be configured. You can configure the lower limit of expected interfaces and the state if the actual number is lower than the expected number. + + diff --git a/.werks/17292.md b/.werks/17292.md new file mode 100644 index 00000000000..bef01a01a77 --- /dev/null +++ b/.werks/17292.md @@ -0,0 +1,25 @@ +[//]: # (werk v2) +# check_httpv2: Body checking: Fixes for inverted regular expressions + +key | value +---------- | --- +date | 2024-09-18T06:26:40+00:00 +version | 2.4.0b1 +class | fix +edition | cre +component | checks +level | 1 +compatible | yes + +The [new active check for HTTP endpoints](https://checkmk.com/werk/15514) offers the option to +search the response body for strings. This search can be configured to either use a fixed string or +a regular expression. In the latter case, there is the additional option to invert the matching: +Report WARNING if the expression matches and OK if not. This werk fixes two issues related to this +option: + +1. In the user interface, the inversion option was incorrectly labelled with "CRITICAL" instead of +"WARNING" in case the expression matches. + +2. In the service output, if inversion was activated, a not-matched expression was labelled with +"matched" and a matched expression with "not matched". Note that the actual service state (WARNING +if matched, OK otherwise) was correct. diff --git a/.werks/17310.md b/.werks/17310.md new file mode 100644 index 00000000000..fef14e12caf --- /dev/null +++ b/.werks/17310.md @@ -0,0 +1,23 @@ +[//]: # (werk v2) +# licensing: Changes in license compatibility + +key | value +---------- | --- +date | 2024-09-24T06:19:35+00:00 +version | 2.4.0b1 +class | feature +edition | cme +component | wato +level | 1 +compatible | no + +Previously, a Checkmk MSP site was usable with a Checkmk Cloud license, but not the other way around. +Since the Checkmk Cloud is now included in the Checkmk MSP, this changes to the following compatibility: + +* Usable with a Checkmk Enterprise license: Checkmk Raw, Checkmk Enterprise +* Usable with a Checkmk Cloud license: Checkmk Raw, Checkmk Enterprise, Checkmk Cloud +* Usable with a Checkmk MSP license: Checkmk Raw, Checkmk Enterprise, Checkmk Cloud, Checkmk MSP + +Note that this only refers to the compatibility when applying a license to a site, not whether sites of different editions can exist together in a distributed setup/monitoring. + +If you plan to upgrade from Checkmk Enterprise to Checkmk Cloud or Enterprise Checkmk Cloud to Checkmk MSP, please upgrade your subscription first. diff --git a/.werks/first_free b/.werks/first_free index dd895343b10..ede2d40793b 100644 --- a/.werks/first_free +++ b/.werks/first_free @@ -1 +1 @@ -17310 +17320 diff --git a/agents/.f12 b/agents/.f12 index 85f40cec549..9ae129c2823 100755 --- a/agents/.f12 +++ b/agents/.f12 @@ -14,6 +14,7 @@ # don't delete *.deb and *.rpm files as those are part of the distribution and can not be found in the source directory rsync --verbose --recursive --links --devices --specials --one-file-system --delete \ + --include='cee/robotmk/*' \ --exclude=.f12* \ --exclude=__init__.py* \ --exclude cmk-agent-ctl \ diff --git a/agents/check_mk_agent.linux b/agents/check_mk_agent.linux index 531253dcb8e..27a1dc61403 100755 --- a/agents/check_mk_agent.linux +++ b/agents/check_mk_agent.linux @@ -1043,9 +1043,15 @@ section_drbd() { } section_heartbeat() { - if [ -S /var/run/heartbeat/crm/cib_ro ] || [ -S /var/run/crm/cib_ro ] || pgrep "^(crmd|pacemaker-contr)$" >/dev/null 2>&1; then - echo '<<>>' - TZ=UTC crm_mon -1 -r | grep -v ^$ | sed 's/^ //; /^\sResource Group:/,$ s/^\s//; s/^\s/_/g' + if command -v crm_mon >/dev/null 2>&1 || [ -S /var/run/heartbeat/crm/cib_ro ] || [ -S /var/run/crm/cib_ro ] || pgrep "^(crmd|pacemaker-contr)$" >/dev/null 2>&1; then + crm_output=$(TZ=UTC crm_mon -1 -r | grep -v ^$ | sed 's/^ //; /^\sResource Group:/,$ s/^\s//; s/^\s/_/g') + if [ -n "$crm_output" ]; then + echo '<<>>' + echo "$crm_output" + else + echo '<<>>' + crm_mon -1 -r 2>&1 + fi fi if inpath cl_status; then diff --git a/agents/modules/windows/build_python.cmd b/agents/modules/windows/build_python.cmd index 3ac1194f92a..89982ff747f 100644 --- a/agents/modules/windows/build_python.cmd +++ b/agents/modules/windows/build_python.cmd @@ -11,8 +11,9 @@ cd %build_msi% 2> nul || powershell Write-Host "cannot find a python sources" - powershell Write-Host "Starting build" -foreground Green set GIT=c:\Program Files\git\cmd\git.exe if not exist "%GIT%" powershell Write-Host "You should install Git as %GIT%" -Foreground Red && exit /b 3 -set HOST_PYTHON=c:\python310\python.exe -if not exist "%HOST_PYTHON%" powershell Write-Host "You should install Python as %HOST_PYTHON%" -Foreground Red && exit /b 4 +for /f %%i in ('where python') do set HOST_PYTHON=%%i +if "%HOST_PYTHON%" == "" powershell Write-Host "Python not found" -Foreground Red && exit /b 4 +powershell Write-Host "Using python %HOST_PYTHON%" -Foreground Green set @echo call buildrelease.bat -o %build_dir% -b -x86 --skip-nuget --skip-pgo --skip-zip diff --git a/buildscripts/scripts/update-architecture-documentation.groovy b/buildscripts/scripts/update-architecture-documentation.groovy index ad6ebb53545..5b183a9dca5 100644 --- a/buildscripts/scripts/update-architecture-documentation.groovy +++ b/buildscripts/scripts/update-architecture-documentation.groovy @@ -8,19 +8,14 @@ def main() { inside_container() { sh("make -C doc/documentation htmlhelp"); } - stage("Stash") { - stash( - name: "htmlhelp", - includes: "doc/documentation/_build/htmlhelp/**" - ); - } } - // The pages produced by the job are served by the web server on our CI - // master node. Extract the results there to make it available to the - // web server. - node("Master_DoNotUse") { - unstash("htmlhelp"); + stage("Deploy") { + withCredentials([file(credentialsId: 'Release_Key', variable: 'RELEASE_KEY')]) { // groovylint-disable DuplicateMapLiteral + sh(""" + scp -rs -o StrictHostKeyChecking=accept-new -i ${RELEASE_KEY} doc/documentation/_build/htmlhelp ${DEV_DOCS_URL}/devdoc + """); + } } } } diff --git a/cmk/base/automations/check_mk.py b/cmk/base/automations/check_mk.py index ea977360609..8c45bb5134f 100644 --- a/cmk/base/automations/check_mk.py +++ b/cmk/base/automations/check_mk.py @@ -1558,11 +1558,11 @@ def _get_service_info_from_autochecks( [ service for node in config_cache.nodes(host_name) - for service in config_cache.get_autochecks_of(node) + for service in config_cache.get_discovered_services(node) if host_name == config_cache.effective_host(node, service.description) ] if host_name in config_cache.hosts_config.clusters - else config_cache.get_autochecks_of(host_name) + else config_cache.get_discovered_services(host_name) ) for service in services: diff --git a/cmk/base/config.py b/cmk/base/config.py index 87e0865d52a..dd8b4ea2d19 100644 --- a/cmk/base/config.py +++ b/cmk/base/config.py @@ -99,7 +99,12 @@ from cmk.fetchers.config import make_persisted_section_dir from cmk.fetchers.filecache import MaxAge -from cmk.checkengine.checking import CheckPluginName, ConfiguredService, ServiceID +from cmk.checkengine.checking import ( + CheckPluginName, + ConfiguredService, + ServiceConfigurer, + ServiceID, +) from cmk.checkengine.discovery import ( AutochecksManager, CheckPreviewEntry, @@ -245,14 +250,20 @@ def _aggregate_check_table_services( skip_ignored=skip_ignored, ) + is_cluster = host_name in config_cache.hosts_config.clusters + # process all entries that are specific to the host # in search (single host) or that might match the host. if not config_cache.is_ping_host(host_name): - yield from (s for s in config_cache.get_autochecks_of(host_name) if sfilter.keep(s)) - - # Now add checks a cluster might receive from its nodes - if host_name in config_cache.hosts_config.clusters: - yield from (s for s in _get_clustered_services(config_cache, host_name) if sfilter.keep(s)) + if is_cluster: + # Add checks a cluster might receive from its nodes + yield from ( + s for s in _get_clustered_services(config_cache, host_name) if sfilter.keep(s) + ) + else: + yield from ( + s for s in config_cache.get_discovered_services(host_name) if sfilter.keep(s) + ) yield from ( svc @@ -349,15 +360,28 @@ def _get_clustered_services( config_cache: ConfigCache, cluster_name: HostName, ) -> Iterable[ConfiguredService]: - for node in config_cache.nodes(cluster_name): - node_checks: list[ConfiguredService] = [] - if not config_cache.is_ping_host(cluster_name): - node_checks += config_cache.get_autochecks_of(node) - node_checks.extend(svc for _, svc in config_cache.enforced_services_table(node).values()) + nodes = config_cache.nodes(cluster_name) + nodes_discovered_services = ( + {} + if config_cache.is_ping_host(cluster_name) + else {node: config_cache.get_discovered_services(node) for node in nodes} + ) + + nodes_enforced_services = {node: config_cache.enforced_services_table(node) for node in nodes} + + # Note: the way we return the services here means that for a service that is enforced on some + # nodes and discovered on others, the parameters of the service on the cluster will depend + # on the order of the nodes in the cluster. + for node in nodes: + yield from ( + service + for service in nodes_discovered_services[node] + if config_cache.effective_host(node, service.description) == cluster_name + ) yield from ( service - for service in node_checks + for _ruleset_name, service in nodes_enforced_services[node].values() if config_cache.effective_host(node, service.description) == cluster_name ) @@ -1924,7 +1948,6 @@ def initialize(self) -> ConfigCache: self._discovered_labels_cache = DiscoveredLabelsCache( self._autochecks_manager.get_autochecks ) - self._clusters_of_cache: dict[HostName, list[HostName]] = {} self._nodes_cache: dict[HostName, list[HostName]] = {} self._effective_host_cache: dict[tuple[HostName, ServiceName, tuple | None], HostName] = {} @@ -1959,6 +1982,11 @@ def initialize(self) -> ConfigCache: if self.is_active(hn) and self.is_online(hn) } ) + self._service_configurer = ServiceConfigurer( + functools.partial(compute_check_parameters, self.ruleset_matcher), + functools.partial(service_description, self.ruleset_matcher), + self.effective_host, + ) return self @@ -2257,7 +2285,7 @@ def enforced_services_table( configured_parameters=TimespecificParameters((params,)), ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), ) @@ -3472,12 +3500,9 @@ def get_explicit_service_custom_variables( except KeyError: return {} - def get_autochecks_of(self, hostname: HostName) -> Sequence[ConfiguredService]: - return self._autochecks_manager.get_configured_services( - hostname, - functools.partial(compute_check_parameters, self.ruleset_matcher), - functools.partial(service_description, self.ruleset_matcher), - self.effective_host, + def get_discovered_services(self, hostname: HostName) -> Sequence[ConfiguredService]: + return self._service_configurer.configure_autochecks( + hostname, self._autochecks_manager.get_autochecks(hostname) ) def section_name_of(self, section: str) -> str: diff --git a/cmk/base/core_nagios/_create_config.py b/cmk/base/core_nagios/_create_config.py index 3288b00af74..a76fe412a5c 100644 --- a/cmk/base/core_nagios/_create_config.py +++ b/cmk/base/core_nagios/_create_config.py @@ -464,8 +464,9 @@ def get_dependencies(hostname: HostName, servicedesc: ServiceName) -> str: ) service_labels[service.description] = { - label.name: label.value for label in service.service_labels.values() - } | dict(get_labels_from_attributes(list(passive_service_attributes.items()))) + **service.discovered_labels, + **get_labels_from_attributes(list(passive_service_attributes.items())), + } service_spec.update(passive_service_attributes) diff --git a/cmk/checkengine/checking/__init__.py b/cmk/checkengine/checking/__init__.py index e2f7fc239fd..001be054c84 100644 --- a/cmk/checkengine/checking/__init__.py +++ b/cmk/checkengine/checking/__init__.py @@ -4,7 +4,14 @@ # conditions defined in the file COPYING, which is part of this source code package. from ._checking import check_host_services, check_plugins_missing_data, execute_checkmk_checks -from ._plugin import AggregatedResult, CheckPlugin, CheckPluginName, ConfiguredService, ServiceID +from ._plugin import ( + AggregatedResult, + CheckPlugin, + CheckPluginName, + ConfiguredService, + ServiceConfigurer, + ServiceID, +) from ._timing import make_timing_results __all__ = [ @@ -16,5 +23,6 @@ "ConfiguredService", "execute_checkmk_checks", "make_timing_results", + "ServiceConfigurer", "ServiceID", ] diff --git a/cmk/checkengine/checking/_plugin.py b/cmk/checkengine/checking/_plugin.py index a6f75b2f8a6..b2503b15878 100644 --- a/cmk/checkengine/checking/_plugin.py +++ b/cmk/checkengine/checking/_plugin.py @@ -5,12 +5,11 @@ from __future__ import annotations -from collections.abc import Mapping, Sequence +from collections.abc import Callable, Iterable, Mapping, Sequence from dataclasses import dataclass from typing import Final, NamedTuple, Protocol from cmk.utils.hostaddress import HostName -from cmk.utils.labels import ServiceLabel from cmk.utils.rulesets import RuleSetName from cmk.utils.servicename import Item, ServiceName from cmk.utils.validatedstr import ValidatedString @@ -25,7 +24,9 @@ "CheckPlugin", "CheckPluginName", "ConfiguredService", + "ServiceConfigurer", "ServiceID", + "AutocheckEntryProtocol", ] @@ -51,15 +52,19 @@ class ServiceID(NamedTuple): item: Item -class ConfiguredService(NamedTuple): - """A service with all information derived from the config""" +@dataclass(frozen=True) +class ConfiguredService: + """A service with almost all information derived from the config + + Currently missing the configured service labels. + """ check_plugin_name: CheckPluginName item: Item description: ServiceName parameters: TimespecificParameters discovered_parameters: Mapping[str, object] - service_labels: Mapping[str, ServiceLabel] + discovered_labels: Mapping[str, str] is_enforced: bool def id(self) -> ServiceID: @@ -74,6 +79,62 @@ def sort_key(self) -> ServiceID: return ServiceID(self.check_plugin_name, self.item or "") +class AutocheckEntryProtocol(Protocol): + @property + def check_plugin_name(self) -> CheckPluginName: ... + + @property + def item(self) -> Item: ... + + @property + def parameters(self) -> Mapping[str, object]: ... + + @property + def service_labels(self) -> Mapping[str, str]: ... + + +class ServiceConfigurer: + def __init__( + self, + compute_check_parameters: Callable[ + [HostName, CheckPluginName, Item, Mapping[str, object]], TimespecificParameters + ], + get_service_description: Callable[[HostName, CheckPluginName, Item], ServiceName], + get_effective_host: Callable[[HostName, str], HostName], + ) -> None: + self._compute_check_parameters = compute_check_parameters + self._get_service_description = get_service_description + self._get_effective_host = get_effective_host + + def _configure_autocheck( + self, hostname: HostName, autocheck_entry: AutocheckEntryProtocol + ) -> ConfiguredService: + # TODO: only call this function when we know "effective host" == hostname and simplify accordingly + service_name = self._get_service_description( + hostname, autocheck_entry.check_plugin_name, autocheck_entry.item + ) + + return ConfiguredService( + check_plugin_name=autocheck_entry.check_plugin_name, + item=autocheck_entry.item, + description=service_name, + parameters=self._compute_check_parameters( + self._get_effective_host(hostname, service_name), + autocheck_entry.check_plugin_name, + autocheck_entry.item, + autocheck_entry.parameters, + ), + discovered_parameters=autocheck_entry.parameters, + discovered_labels=autocheck_entry.service_labels, + is_enforced=False, + ) + + def configure_autochecks( + self, hostname: HostName, autocheck_entries: Iterable[AutocheckEntryProtocol] + ) -> Sequence[ConfiguredService]: + return [self._configure_autocheck(hostname, entry) for entry in autocheck_entries] + + @dataclass(frozen=True) class AggregatedResult: service: ConfiguredService diff --git a/cmk/checkengine/discovery/_autochecks.py b/cmk/checkengine/discovery/_autochecks.py index 6e137a64e69..d329c6809b1 100644 --- a/cmk/checkengine/discovery/_autochecks.py +++ b/cmk/checkengine/discovery/_autochecks.py @@ -145,59 +145,18 @@ class AutochecksManager: """Read autochecks from the configuration Autochecks of a host are once read and cached for the whole lifetime of the - AutochecksManager.""" + AutochecksManager. + + When trying to remove this cache (which we should consider), make sure to keep + the case of overlapping clusters in mind. Autochecks of a node might be read + multiple times (to a degree where it's not accepteble). + """ def __init__(self) -> None: super().__init__() self._configured_services_cache: dict[HostName, Sequence[ConfiguredService]] = {} self._raw_autochecks_cache: dict[HostName, Sequence[AutocheckEntry]] = {} - def get_configured_services( - self, - hostname: HostName, - compute_check_parameters: ComputeCheckParameters, - get_service_description: GetServiceDescription, - get_effective_host: GetEffectiveHost, - ) -> Sequence[ConfiguredService]: - if hostname not in self._configured_services_cache: - self._configured_services_cache[hostname] = list( - self._get_autochecks_of_uncached( - hostname, - compute_check_parameters, - get_service_description, - get_effective_host, - ) - ) - return self._configured_services_cache[hostname] - - def _get_autochecks_of_uncached( - self, - hostname: HostName, - compute_check_parameters: ComputeCheckParameters, - get_service_description: GetServiceDescription, - get_effective_host: GetEffectiveHost, - ) -> Iterable[ConfiguredService]: - """Read automatically discovered checks of one host""" - for autocheck_entry in self.get_autochecks(hostname): - service_name = get_service_description(hostname, *autocheck_entry.id()) - - yield ConfiguredService( - check_plugin_name=autocheck_entry.check_plugin_name, - item=autocheck_entry.item, - description=service_name, - parameters=compute_check_parameters( - get_effective_host(hostname, service_name), - *autocheck_entry.id(), - autocheck_entry.parameters, - ), - discovered_parameters=autocheck_entry.parameters, - service_labels={ - name: ServiceLabel(name, value) - for name, value in autocheck_entry.service_labels.items() - }, - is_enforced=False, - ) - def get_autochecks( self, hostname: HostName, diff --git a/cmk/checkengine/discovery/_autodiscovery.py b/cmk/checkengine/discovery/_autodiscovery.py index 6a331c9bf5c..6f065ec64e0 100644 --- a/cmk/checkengine/discovery/_autodiscovery.py +++ b/cmk/checkengine/discovery/_autodiscovery.py @@ -642,17 +642,6 @@ def get_host_services_by_host_name( } } - for h, services in services_by_host_name.items(): - services_by_host_name[h].update( - _reclassify_disabled_items( - host_name, - services, - ignore_service, - ignore_plugin, - get_service_description, - ) - ) - # remove the ones shadowed by enforced services return { h: _group_by_transition({k: v for k, v in s.items() if k not in enforced_services}) @@ -730,8 +719,14 @@ def _node_service_source( service_name: ServiceName, ) -> _Transition: if host_name == cluster_name: - return check_source + return ( + "ignored" + if ignore_plugin(host_name, check_plugin_name) + or ignore_service(host_name, service_name) + else check_source + ) + # TODO: this does not make much sense. If the service is clustered, but ignored _on that cluster_, it should be shown there. if ignore_service(cluster_name, service_name) or ignore_plugin(cluster_name, check_plugin_name): return "ignored" @@ -829,6 +824,15 @@ def _get_cluster_services( ), ) ) + cluster_items[host_name].update( + _reclassify_disabled_items( + host_name, + cluster_items[host_name], + ignore_service, + ignore_plugin, + get_service_description, + ) + ) return cluster_items diff --git a/cmk/checkengine/discovery/_preview.py b/cmk/checkengine/discovery/_preview.py index a3dde498769..eee4ac32535 100644 --- a/cmk/checkengine/discovery/_preview.py +++ b/cmk/checkengine/discovery/_preview.py @@ -13,7 +13,7 @@ import cmk.utils.paths from cmk.utils import tty from cmk.utils.hostaddress import HostAddress, HostName -from cmk.utils.labels import DiscoveredHostLabelsStore, HostLabel, ServiceLabel +from cmk.utils.labels import DiscoveredHostLabelsStore, HostLabel from cmk.utils.log import console from cmk.utils.rulesets.ruleset_matcher import RulesetName from cmk.utils.sectionname import SectionMap, SectionName @@ -185,17 +185,11 @@ def get_check_preview( description=find_service_description(h, *DiscoveredService.id(entry)), parameters=compute_check_parameters(h, DiscoveredService.older(entry)), discovered_parameters=DiscoveredService.older(entry).parameters, - service_labels={ - n: ServiceLabel(n, v) - for n, v in DiscoveredService.older(entry).service_labels.items() - }, + discovered_labels=DiscoveredService.older(entry).service_labels, is_enforced=False, ), new_discovered_parameters=DiscoveredService.newer(entry).parameters, - new_service_labels={ - n: ServiceLabel(n, v) - for n, v in DiscoveredService.newer(entry).service_labels.items() - }, + new_service_labels=DiscoveredService.newer(entry).service_labels, check_source=check_source, providers=providers, found_on_nodes=found_on_nodes, @@ -233,7 +227,7 @@ def _check_preview_table_row( *, service: ConfiguredService, new_discovered_parameters: Mapping[str, object], - new_service_labels: Mapping[str, ServiceLabel], + new_service_labels: Mapping[str, str], check_plugins: Mapping[CheckPluginName, CheckPlugin], check_source: _Transition | Literal["manual"], providers: Mapping[HostKey, Provider], @@ -274,7 +268,7 @@ def make_output() -> str: state=result.state, output=make_output(), metrics=[], - old_labels={l.name: l.value for l in service.service_labels.values()}, - new_labels={l.name: l.value for l in new_service_labels.values()}, + old_labels=service.discovered_labels, + new_labels=new_service_labels, found_on_nodes=list(found_on_nodes), ) diff --git a/cmk/gui/graphing/_graph_templates.py b/cmk/gui/graphing/_graph_templates.py index 6da6270c06b..eba5a570ee9 100644 --- a/cmk/gui/graphing/_graph_templates.py +++ b/cmk/gui/graphing/_graph_templates.py @@ -563,25 +563,6 @@ def metric_expression_to_graph_recipe_expression( ) -def _evaluate_title(title: str, translated_metrics: Mapping[str, TranslatedMetric]) -> str: - """Replace expressions in strings like CPU Load - %(load1:max@count) CPU Cores""" - # Note: The 'CPU load' graph is the only example with such a replacement. We do not want to - # offer such replacements in a generic way. - reg = regex.regex(r"%\([^)]*\)") - if m := reg.search(title): - if ( - result := parse_legacy_simple_expression(m.group()[2:-1], translated_metrics).evaluate( - translated_metrics - ) - ).is_error(): - return title.split("-")[0].strip() - return reg.sub( - get_render_function(result.ok.unit_spec)(result.ok.value).strip(), - title, - ) - return title - - def _evaluate_graph_template_range_boundary( base_metric_expression: BaseMetricExpression, translated_metrics: Mapping[str, TranslatedMetric] ) -> float | None: @@ -611,30 +592,6 @@ def evaluate_graph_template_range( assert_never(graph_template_range) -def _horizontal_rules_from_thresholds( - metric_expressions: Iterable[MetricExpression], - translated_metrics: Mapping[str, TranslatedMetric], -) -> Sequence[HorizontalRule]: - horizontal_rules = [] - for metric_expression in metric_expressions: - if (result := metric_expression.evaluate(translated_metrics)).is_error(): - # Scalar value like min and max are always optional. This makes configuration - # of graphs easier. - if result.error.metric_name: - continue - return [] - - horizontal_rules.append( - HorizontalRule( - value=result.ok.value, - rendered_value=get_render_function(result.ok.unit_spec)(result.ok.value), - color=result.ok.color, - title=metric_expression.title, - ) - ) - return horizontal_rules - - def _create_graph_recipe_from_template( site_id: SiteId, host_name: HostName, @@ -653,7 +610,7 @@ def _create_graph_recipe_from_template( service_name, evaluated.base, translated_metrics, - graph_template.consolidation_function or "max", + graph_template.consolidation_function, ), unit=( evaluated.unit_spec @@ -676,7 +633,7 @@ def _create_graph_recipe_from_template( % ", ".join(repr(unit) for unit in units) ) - title = _evaluate_title(graph_template.title or "", translated_metrics) + title = graph_template.title if not title: title = next((m.title for m in metrics), "") @@ -694,18 +651,24 @@ def _create_graph_recipe_from_template( graph_template.range, translated_metrics, ), - horizontal_rules=_horizontal_rules_from_thresholds( - graph_template.scalars, translated_metrics - ), # e.g. lines for WARN and CRIT + horizontal_rules=[ + HorizontalRule( + value=evaluated.value, + rendered_value=get_render_function(evaluated.unit_spec)(evaluated.value), + color=evaluated.color, + title=evaluated.title, + ) + for evaluated in graph_template.scalars + ], omit_zero_metrics=graph_template.omit_zero_metrics, - consolidation_function=graph_template.consolidation_function or "max", + consolidation_function=graph_template.consolidation_function, specification=specification, ) def _evaluate_predictive_metrics( - translated_metrics: Mapping[str, TranslatedMetric], evaluated_metrics: Sequence[Evaluated], + translated_metrics: Mapping[str, TranslatedMetric], ) -> Iterator[Evaluated]: computed = set() for evaluated in evaluated_metrics: @@ -724,17 +687,68 @@ def _evaluate_predictive_metrics( yield result.ok +def _evaluate_title(title: str, translated_metrics: Mapping[str, TranslatedMetric]) -> str: + """Replace expressions in strings like CPU Load - %(load1:max@count) CPU Cores""" + # Note: The 'CPU load' graph is the only example with such a replacement. We do not want to + # offer such replacements in a generic way. + reg = regex.regex(r"%\([^)]*\)") + if m := reg.search(title): + if ( + result := parse_legacy_simple_expression(m.group()[2:-1], translated_metrics).evaluate( + translated_metrics + ) + ).is_error(): + return title.split("-")[0].strip() + return reg.sub( + get_render_function(result.ok.unit_spec)(result.ok.value).strip(), + title, + ) + return title + + +def _evaluate_scalars( + metric_expressions: Sequence[MetricExpression], + translated_metrics: Mapping[str, TranslatedMetric], +) -> Sequence[Evaluated]: + results = [] + for metric_expression in metric_expressions: + if (result := metric_expression.evaluate(translated_metrics)).is_error(): + # Scalar value like min and max are always optional. This makes configuration + # of graphs easier. + if result.error.metric_name: + continue + return [] + results.append(result.ok) + return results + + @dataclass(frozen=True) class EvaluatedGraphTemplate: id: str title: str - scalars: Sequence[MetricExpression] - consolidation_function: GraphConsolidationFunction | None + scalars: Sequence[Evaluated] + consolidation_function: GraphConsolidationFunction range: FixedGraphTemplateRange | MinimalGraphTemplateRange | None omit_zero_metrics: bool metrics: Sequence[Evaluated] +def _create_evaluated_graph_template( + graph_template: GraphTemplate, + evaluated_metrics: Sequence[Evaluated], + translated_metrics: Mapping[str, TranslatedMetric], +) -> EvaluatedGraphTemplate: + return EvaluatedGraphTemplate( + id=graph_template.id, + title=_evaluate_title(graph_template.title, translated_metrics), + scalars=_evaluate_scalars(graph_template.scalars, translated_metrics), + consolidation_function=graph_template.consolidation_function or "max", + range=graph_template.range, + omit_zero_metrics=graph_template.omit_zero_metrics, + metrics=evaluated_metrics, + ) + + def _get_evaluated_graph_templates( translated_metrics: Mapping[str, TranslatedMetric], ) -> Iterator[EvaluatedGraphTemplate]: @@ -742,34 +756,27 @@ def _get_evaluated_graph_templates( yield from () return - def _generate_graph_templates( - graph_template: GraphTemplate, - ) -> Iterator[EvaluatedGraphTemplate]: - if evaluated_metrics := evaluate_metrics( - conflicting_metrics=graph_template.conflicting_metrics, - optional_metrics=graph_template.optional_metrics, - metric_expressions=graph_template.metrics, - translated_metrics=translated_metrics, - ): - yield EvaluatedGraphTemplate( - id=graph_template.id, - title=graph_template.title, - scalars=graph_template.scalars, - consolidation_function=graph_template.consolidation_function, - range=graph_template.range, - omit_zero_metrics=graph_template.omit_zero_metrics, - metrics=list( - itertools.chain( - evaluated_metrics, - _evaluate_predictive_metrics(translated_metrics, evaluated_metrics), - ) - ), - ) - graph_templates = [ - t + _create_evaluated_graph_template( + graph_template, + list( + itertools.chain( + evaluated_metrics, + _evaluate_predictive_metrics(evaluated_metrics, translated_metrics), + ) + ), + translated_metrics, + ) for id_, graph_plugin in _get_graph_plugins() - for t in _generate_graph_templates(_parse_graph_plugin(id_, graph_plugin)) + for graph_template in [_parse_graph_plugin(id_, graph_plugin)] + if ( + evaluated_metrics := evaluate_metrics( + conflicting_metrics=graph_template.conflicting_metrics, + optional_metrics=graph_template.optional_metrics, + metric_expressions=graph_template.metrics, + translated_metrics=translated_metrics, + ) + ) ] yield from graph_templates @@ -779,19 +786,15 @@ def _generate_graph_templates( for metric_name, translated_metric in sorted(translated_metrics.items()): if translated_metric.auto_graph and metric_name not in already_graphed_metrics: graph_template = _create_graph_template_from_name(metric_name) - yield EvaluatedGraphTemplate( - id=graph_template.id, - title=graph_template.title, - scalars=graph_template.scalars, - consolidation_function=graph_template.consolidation_function, - range=graph_template.range, - omit_zero_metrics=graph_template.omit_zero_metrics, - metrics=evaluate_metrics( + yield _create_evaluated_graph_template( + graph_template, + evaluate_metrics( conflicting_metrics=graph_template.conflicting_metrics, optional_metrics=graph_template.optional_metrics, metric_expressions=graph_template.metrics, translated_metrics=translated_metrics, ), + translated_metrics, ) @@ -816,19 +819,15 @@ def _matching_graph_templates( graph_template = _create_graph_template_from_name(graph_id) yield ( 0, - EvaluatedGraphTemplate( - id=graph_template.id, - title=graph_template.title, - scalars=graph_template.scalars, - consolidation_function=graph_template.consolidation_function, - range=graph_template.range, - omit_zero_metrics=graph_template.omit_zero_metrics, - metrics=evaluate_metrics( + _create_evaluated_graph_template( + graph_template, + evaluate_metrics( conflicting_metrics=graph_template.conflicting_metrics, optional_metrics=graph_template.optional_metrics, metric_expressions=graph_template.metrics, translated_metrics=translated_metrics, ), + translated_metrics, ), ) return diff --git a/cmk/gui/plugins/wato/check_parameters/bonding.py b/cmk/gui/plugins/wato/check_parameters/bonding.py deleted file mode 100644 index b1c49063e49..00000000000 --- a/cmk/gui/plugins/wato/check_parameters/bonding.py +++ /dev/null @@ -1,95 +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.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, -) -from cmk.gui.valuespec import Dictionary, DropdownChoice, MonitoringState, TextInput - - -def get_common_elements() -> list: - return [ - ( - "expect_active", - DropdownChoice( - title=_("Warn on unexpected active interface"), - choices=[ - ("ignore", _("ignore which one is active")), - ("primary", _("require primary interface to be active")), - ("lowest", _("require interface that sorts lowest alphabetically")), - ], - default_value="ignore", - ), - ), - ( - "ieee_302_3ad_agg_id_missmatch_state", - MonitoringState( - title=_("State for mismatching Aggregator IDs for LACP"), - default_value=1, - ), - ), - ] - - -def _parameter_valuespec_lnx_bonding(): - return Dictionary( - elements=[ - *get_common_elements(), - ( - "bonding_mode_states", - Dictionary( - title=_("State for specific bonding modes"), - optional_keys=[], - elements=[ - ("mode_0", MonitoringState(title=_("balance-rr"), default_value=0)), - ("mode_1", MonitoringState(title=_("active-backup"), default_value=0)), - ("mode_2", MonitoringState(title=_("balance-xor"), default_value=0)), - ("mode_3", MonitoringState(title=_("broadcast"), default_value=0)), - ("mode_4", MonitoringState(title=_("802.3ad"), default_value=0)), - ("mode_5", MonitoringState(title=_("balance-tlb"), default_value=0)), - ("mode_6", MonitoringState(title=_("balance-alb"), default_value=0)), - ], - help=_( - "Specify the monitoring state when the bonding mode is not as expected." - ), - ), - ), - ], - ignored_keys=["primary"], - ) - - -def _parameter_valuespec_ovs_bonding(): - return Dictionary( - elements=get_common_elements(), - ignored_keys=["primary"], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name="bonding", - group=RulespecGroupCheckParametersNetworking, - item_spec=lambda: TextInput(title=_("Name of the bonding interface")), - match_type="dict", - parameter_valuespec=_parameter_valuespec_lnx_bonding, - title=lambda: _("Linux bonding interface status"), - ) -) - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name="ovs_bonding", - group=RulespecGroupCheckParametersNetworking, - item_spec=lambda: TextInput(title=_("Name of the bonding interface")), - match_type="dict", - parameter_valuespec=_parameter_valuespec_ovs_bonding, - title=lambda: _("OVS bonding interface status"), - ) -) diff --git a/cmk/gui/quick_setup/_modes.py b/cmk/gui/quick_setup/_modes.py index 3c97aa420f8..84a72ce479f 100644 --- a/cmk/gui/quick_setup/_modes.py +++ b/cmk/gui/quick_setup/_modes.py @@ -214,8 +214,7 @@ def page(self) -> None: def _bundles_listing(self, group_name: str) -> None: bundle_ids = set(load_group_bundles(group_name).keys()) if not bundle_ids: - # TODO (CMK-18347): add redesigned overview for empty configurations - html.div(_("No configuration yet")) + self._no_bundles() return bundles_with_references = identify_bundle_references(group_name, bundle_ids) @@ -225,6 +224,28 @@ def _bundles_listing(self, group_name: str) -> None: raise MKGeneralException("Not implemented") + def _no_bundles(self) -> None: + if self._bundle_group_type is RuleGroupType.SPECIAL_AGENTS: + subtype = self._name.split(":", maxsplit=1)[1] + html.div( + html.render_icon(f"qs_{subtype}") + + html.render_b(_("No %s configuration yet") % self.title()) + + html.render_p( + _( + 'Click the "Add configuration" button to start setting up your first ' + "configuration." + ) + ) + + html.render_a( + _("Add configuration"), + mode_url(ModeQuickSetupSpecialAgent.name(), varname=self._name), + ), + css=["no-config-bundles"], + ) + return + + raise MKGeneralException("Not implemented") + def _action_url(self, action: str, bundle_id: BundleId) -> str: vars_: HTTPVariables = [ ("mode", request.var("mode", self.name())), diff --git a/cmk/gui/quick_setup/config_setups/aws/form_specs.py b/cmk/gui/quick_setup/config_setups/aws/form_specs.py index 111d68f37ae..04e1f4f91b7 100644 --- a/cmk/gui/quick_setup/config_setups/aws/form_specs.py +++ b/cmk/gui/quick_setup/config_setups/aws/form_specs.py @@ -5,11 +5,7 @@ from collections.abc import Mapping, Sequence -from cmk.ccc.version import Edition - -from cmk.gui.form_specs.vue.visitors._registry import form_spec_registry from cmk.gui.quick_setup.config_setups.aws.ruleset_helper import formspec_aws_tags -from cmk.gui.utils.rule_specs.loader import LoadedRuleSpec from cmk.plugins.aws.lib import aws_region_to_monitor # pylint: disable=cmk-module-layer-violation @@ -40,7 +36,6 @@ String, validators, ) -from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic def _global_services() -> Sequence[MultipleChoiceElement]: @@ -271,7 +266,7 @@ def quick_setup_advanced() -> Mapping[str, DictElement]: } -def _formspec(): +def quick_setup_aws_form_spec(): return Dictionary( title=Title("Amazon Web Services (AWS)"), elements={ @@ -281,15 +276,3 @@ def _formspec(): **quick_setup_advanced(), }, ) - - -# Add the form spec to the registry but don't register the rule spec. -form_spec_registry["aws"] = LoadedRuleSpec( - rule_spec=SpecialAgent( - name="aws", - title=Title("Amazon Web Services (AWS)"), - topic=Topic.CLOUD, - parameter_form=_formspec, - ), - edition_only=Edition.CRE, -) diff --git a/cmk/gui/quick_setup/config_setups/aws/stages.py b/cmk/gui/quick_setup/config_setups/aws/stages.py index c42dc41f843..596b67413e8 100644 --- a/cmk/gui/quick_setup/config_setups/aws/stages.py +++ b/cmk/gui/quick_setup/config_setups/aws/stages.py @@ -13,6 +13,7 @@ from cmk.gui.form_specs.vue.shared_type_defs import DictionaryLayout from cmk.gui.quick_setup.config_setups.aws import form_specs as aws from cmk.gui.quick_setup.config_setups.aws import ruleset_helper +from cmk.gui.quick_setup.config_setups.aws.form_specs import quick_setup_aws_form_spec from cmk.gui.quick_setup.v0_unstable.predefined import ( collect_params_from_form_data, complete, @@ -36,6 +37,7 @@ from cmk.rulesets.v1.form_specs import ( DefaultValue, DictElement, + Dictionary, InputHint, SingleChoice, SingleChoiceElement, @@ -187,13 +189,16 @@ def review_and_run_preview_service_discovery() -> QuickSetupStage: configure_components=[], custom_validators=[ qs_validators.validate_test_connection_custom_collect_params( - RuleGroup.SpecialAgents("aws"), custom_collect_params=aws_collect_params + rulespec_name=RuleGroup.SpecialAgents("aws"), + parameter_form=quick_setup_aws_form_spec(), + custom_collect_params=aws_collect_params, ) ], recap=[ recaps.recap_service_discovery_custom_collect_params( - RuleGroup.SpecialAgents("aws"), - [ServiceInterest(".*", "services")], + rulespec_name=RuleGroup.SpecialAgents("aws"), + parameter_form=quick_setup_aws_form_spec(), + services_of_interest=[ServiceInterest(".*", "services")], custom_collect_params=aws_collect_params, ) ], @@ -204,15 +209,18 @@ def review_and_run_preview_service_discovery() -> QuickSetupStage: def save_action(all_stages_form_data: ParsedFormData) -> str: return complete.create_and_save_special_agent_bundle_custom_collect_params( special_agent_name="aws", + parameter_form=quick_setup_aws_form_spec(), all_stages_form_data=all_stages_form_data, custom_collect_params=aws_collect_params, ) def aws_collect_params( - all_stages_form_data: ParsedFormData, rulespec_name: str + all_stages_form_data: ParsedFormData, parameter_form: Dictionary ) -> Mapping[str, object]: - return aws_transform_to_disk(collect_params_from_form_data(all_stages_form_data, rulespec_name)) + return aws_transform_to_disk( + collect_params_from_form_data(all_stages_form_data, parameter_form) + ) def _migrate_aws_service(service: str) -> object: @@ -241,7 +249,8 @@ def aws_transform_to_disk(params: Mapping[str, object]) -> Mapping[str, object]: "access_key_id": params["access_key_id"], "secret_access_key": params["secret_access_key"], "global_services": {k: _migrate_aws_service(k) for k in global_services}, - "regions_to_monitor": [region.replace("_", "-") for region in regions_to_monitor], + "regions": [region.replace("_", "-") for region in regions_to_monitor], + "access": {}, # TODO required key but not yet implemented. It's part of quick_setup_advanced() "services": {keys_to_rename.get(k, k): _migrate_aws_service(k) for k in services}, "piggyback_naming_convention": "ip_region_instance", "overall_tags": [(tag["key"], tag["values"]) for tag in overall_tags], diff --git a/cmk/gui/quick_setup/v0_unstable/predefined/_common.py b/cmk/gui/quick_setup/v0_unstable/predefined/_common.py index f8e545d96c9..d24402b95c5 100644 --- a/cmk/gui/quick_setup/v0_unstable/predefined/_common.py +++ b/cmk/gui/quick_setup/v0_unstable/predefined/_common.py @@ -15,7 +15,6 @@ from cmk.gui.form_specs.vue.form_spec_visitor import serialize_data_for_frontend from cmk.gui.form_specs.vue.visitors import DataOrigin -from cmk.gui.form_specs.vue.visitors._registry import form_spec_registry from cmk.gui.quick_setup.v0_unstable.setups import QuickSetupStage from cmk.gui.quick_setup.v0_unstable.type_defs import ParsedFormData, ServiceInterest from cmk.gui.quick_setup.v0_unstable.widgets import ( @@ -31,19 +30,17 @@ def _collect_params_with_defaults_from_form_data( - all_stages_form_data: ParsedFormData, rulespec_name: str + all_stages_form_data: ParsedFormData, parameter_form: Dictionary ) -> Mapping[str, object]: return _add_defaults_to_form_data( - _get_rule_defaults(rulespec_name), - _collect_params_from_form_data(all_stages_form_data, rulespec_name), + _get_rule_defaults(parameter_form), + _collect_params_from_form_data(all_stages_form_data, parameter_form), ) def _collect_passwords_from_form_data( - all_stages_form_data: ParsedFormData, rulespec_name: str + all_stages_form_data: ParsedFormData, parameter_form: Dictionary ) -> Mapping[str, str]: - if (parameter_form := _get_parameter_form_from_rulespec_name(rulespec_name)) is None: - return {} possible_expected_password_keys = [ key for key in parameter_form.elements.keys() @@ -115,16 +112,10 @@ def _add_defaults_to_form_data( } -def _get_parameter_form_from_rulespec_name(rulespec_name: str) -> Dictionary | None: - _parameter_form = form_spec_registry[rulespec_name.split(":")[1]].rule_spec.parameter_form - return _parameter_form() if callable(_parameter_form) else _parameter_form - - def _collect_params_from_form_data( - all_stages_form_data: ParsedFormData, rulespec_name: str + all_stages_form_data: ParsedFormData, + parameter_form: Dictionary, ) -> Mapping[str, object]: - if (parameter_form := _get_parameter_form_from_rulespec_name(rulespec_name)) is None: - return {} possible_expected_keys = parameter_form.elements.keys() return { @@ -136,10 +127,7 @@ def _collect_params_from_form_data( } -def _get_rule_defaults(rulespec_name: str) -> dict[str, object]: - if (parameter_form := _get_parameter_form_from_rulespec_name(rulespec_name)) is None: - return {} - +def _get_rule_defaults(parameter_form: Dictionary) -> dict[str, object]: return serialize_data_for_frontend( form_spec=parameter_form, field_id="rule_id", diff --git a/cmk/gui/quick_setup/v0_unstable/predefined/_complete.py b/cmk/gui/quick_setup/v0_unstable/predefined/_complete.py index e75d067a639..c5a56a779de 100644 --- a/cmk/gui/quick_setup/v0_unstable/predefined/_complete.py +++ b/cmk/gui/quick_setup/v0_unstable/predefined/_complete.py @@ -12,9 +12,10 @@ from cmk.utils.global_ident_type import GlobalIdent, PROGRAM_ID_QUICK_SETUP from cmk.utils.hostaddress import HostName +from cmk.utils.password_store import ad_hoc_password_id from cmk.utils.password_store import Password as StorePassword from cmk.utils.rulesets.definition import RuleGroup -from cmk.utils.rulesets.ruleset_matcher import RuleConditionsSpec, RuleSpec +from cmk.utils.rulesets.ruleset_matcher import RuleConditionsSpec, RuleOptionsSpec, RuleSpec from cmk.gui.config import active_config from cmk.gui.http import request @@ -51,6 +52,8 @@ perform_fix_all, ) +from cmk.rulesets.v1.form_specs import Dictionary + def _normalize_folder_path_str(folder_path: str) -> str: r"""Normalizes a folder representation @@ -182,28 +185,33 @@ def create_rule( host_name=[host_name], host_folder=host_path, ), + options=RuleOptionsSpec(disabled=False, description=""), ), ) def create_and_save_special_agent_bundle( special_agent_name: str, + parameter_form: Dictionary, all_stages_form_data: ParsedFormData, ) -> str: return _create_and_save_special_agent_bundle( special_agent_name=special_agent_name, all_stages_form_data=all_stages_form_data, collect_params=_collect_params_with_defaults_from_form_data, + parameter_form=parameter_form, ) def create_and_save_special_agent_bundle_custom_collect_params( special_agent_name: str, + parameter_form: Dictionary, all_stages_form_data: ParsedFormData, - custom_collect_params: Callable[[ParsedFormData, str], Mapping[str, object]], + custom_collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], ) -> str: return _create_and_save_special_agent_bundle( special_agent_name=special_agent_name, + parameter_form=parameter_form, all_stages_form_data=all_stages_form_data, collect_params=custom_collect_params, ) @@ -211,8 +219,9 @@ def create_and_save_special_agent_bundle_custom_collect_params( def _create_and_save_special_agent_bundle( special_agent_name: str, + parameter_form: Dictionary, all_stages_form_data: ParsedFormData, - collect_params: Callable[[ParsedFormData, str], Mapping[str, object]], + collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], ) -> str: rulespec_name = RuleGroup.SpecialAgents(special_agent_name) bundle_id = _find_unique_id(form_data=all_stages_form_data, target_key=UniqueBundleIDStr) @@ -224,8 +233,21 @@ def _create_and_save_special_agent_bundle( site_selection = _find_unique_id(all_stages_form_data, "site_selection") site_id = SiteId(site_selection) if site_selection else omd_site() - params = collect_params(all_stages_form_data, rulespec_name) - passwords = _collect_passwords_from_form_data(all_stages_form_data, rulespec_name) + params = collect_params(all_stages_form_data, parameter_form) + + # TODO: Find a better solution. + # Here we replace the password id if the user has selected from password store + # otherwise, the previous one will be overwritten. + if all_stages_form_data[FormSpecId("credentials")]["secret_access_key"][1] == "stored_password": + all_stages_form_data[FormSpecId("credentials")]["secret_access_key"] = ( + "explicit_password", + all_stages_form_data[FormSpecId("credentials")]["secret_access_key"][1], + ( + ad_hoc_password_id(), + all_stages_form_data[FormSpecId("credentials")]["secret_access_key"][2][1], + ), + ) + passwords = _collect_passwords_from_form_data(all_stages_form_data, parameter_form) # TODO: DCD still to be implemented cmk-18341 diff --git a/cmk/gui/quick_setup/v0_unstable/predefined/_recaps.py b/cmk/gui/quick_setup/v0_unstable/predefined/_recaps.py index f712fec105e..3e79843bd84 100644 --- a/cmk/gui/quick_setup/v0_unstable/predefined/_recaps.py +++ b/cmk/gui/quick_setup/v0_unstable/predefined/_recaps.py @@ -36,6 +36,8 @@ from cmk.gui.quick_setup.v0_unstable.widgets import FormSpecRecap, ListOfWidgets, Text, Widget from cmk.gui.watolib.check_mk_automations import special_agent_discovery_preview +from cmk.rulesets.v1.form_specs import Dictionary + def recaps_form_spec( quick_setup_id: QuickSetupId, @@ -66,12 +68,14 @@ def recaps_form_spec( def recap_service_discovery_custom_collect_params( rulespec_name: str, + parameter_form: Dictionary, services_of_interest: Sequence[ServiceInterest], - custom_collect_params: Callable[[ParsedFormData, str], Mapping[str, object]], + custom_collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], ) -> CallableRecap: return partial( _recap_service_discovery, rulespec_name, + parameter_form, services_of_interest, custom_collect_params, ) @@ -79,11 +83,13 @@ def recap_service_discovery_custom_collect_params( def recap_service_discovery( rulespec_name: str, + parameter_form: Dictionary, services_of_interest: Sequence[ServiceInterest], ) -> CallableRecap: return partial( _recap_service_discovery, rulespec_name, + parameter_form, services_of_interest, _collect_params_with_defaults_from_form_data, ) @@ -91,14 +97,15 @@ def recap_service_discovery( def _recap_service_discovery( rulespec_name: str, + parameter_form: Dictionary, services_of_interest: Sequence[ServiceInterest], - collect_params: Callable[[ParsedFormData, str], Mapping[str, object]], + collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], _quick_setup_id: QuickSetupId, _stage_index: StageIndex, all_stages_form_data: ParsedFormData, ) -> Sequence[Widget]: - params = collect_params(all_stages_form_data, rulespec_name) - passwords = _collect_passwords_from_form_data(all_stages_form_data, rulespec_name) + params = collect_params(all_stages_form_data, parameter_form) + passwords = _collect_passwords_from_form_data(all_stages_form_data, parameter_form) site_id = _find_unique_id(all_stages_form_data, "site_selection") host_name = _find_unique_id(all_stages_form_data, "host_name") diff --git a/cmk/gui/quick_setup/v0_unstable/predefined/_validators.py b/cmk/gui/quick_setup/v0_unstable/predefined/_validators.py index 6cd5acf02a1..76ce038a122 100644 --- a/cmk/gui/quick_setup/v0_unstable/predefined/_validators.py +++ b/cmk/gui/quick_setup/v0_unstable/predefined/_validators.py @@ -27,28 +27,38 @@ from cmk.gui.watolib.check_mk_automations import diag_special_agent from cmk.gui.watolib.configuration_bundles import ConfigBundleStore +from cmk.rulesets.v1.form_specs import Dictionary + def validate_test_connection_custom_collect_params( - rulespec_name: str, custom_collect_params: Callable[[ParsedFormData, str], Mapping[str, object]] + rulespec_name: str, + parameter_form: Dictionary, + custom_collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], ) -> CallableValidator: return partial( _validate_test_connection, rulespec_name, + parameter_form, custom_collect_params, ) -def validate_test_connection(rulespec_name: str) -> CallableValidator: +def validate_test_connection( + rulespec_name: str, + parameter_form: Dictionary, +) -> CallableValidator: return partial( _validate_test_connection, rulespec_name, + parameter_form, _collect_params_with_defaults_from_form_data, ) def _validate_test_connection( rulespec_name: str, - collect_params: Callable[[ParsedFormData, str], Mapping[str, object]], + parameter_form: Dictionary, + collect_params: Callable[[ParsedFormData, Dictionary], Mapping[str, object]], _quick_setup_id: QuickSetupId, _stage_index: StageIndex, all_stages_form_data: ParsedFormData, @@ -56,8 +66,8 @@ def _validate_test_connection( general_errors: GeneralStageErrors = [] site_id = _find_unique_id(all_stages_form_data, "site_selection") host_name = _find_unique_id(all_stages_form_data, "host_name") - params = collect_params(all_stages_form_data, rulespec_name) - passwords = _collect_passwords_from_form_data(all_stages_form_data, rulespec_name) + params = collect_params(all_stages_form_data, parameter_form) + passwords = _collect_passwords_from_form_data(all_stages_form_data, parameter_form) output = diag_special_agent( SiteId(site_id) if site_id else omd_site(), _create_diag_special_agent_input( diff --git a/cmk/gui/wato/pages/global_settings.py b/cmk/gui/wato/pages/global_settings.py index 36ef5a9c693..f36831eba0b 100644 --- a/cmk/gui/wato/pages/global_settings.py +++ b/cmk/gui/wato/pages/global_settings.py @@ -52,7 +52,7 @@ ConfigVariable, ConfigVariableGroup, ) -from cmk.gui.watolib.config_domains import ConfigDomainCore +from cmk.gui.watolib.config_domains import ConfigDomainCACertificates, ConfigDomainCore from cmk.gui.watolib.global_settings import load_configuration_settings, save_global_settings from cmk.gui.watolib.hosts_and_folders import folder_preserving_link from cmk.gui.watolib.mode import mode_url, ModeRegistry, redirect, WatoMode @@ -360,6 +360,7 @@ def action(self) -> ActionResult: new_value = self._valuespec.from_html_vars("ve") self._valuespec.validate_value(new_value, "ve") + current = self._current_settings[self._varname] self._current_settings[self._varname] = new_value msg = HTML.without_escaping( _("Changed global configuration variable %s to %s.") @@ -370,6 +371,8 @@ def action(self) -> ActionResult: ) self._save() + if self._varname == "trusted_certificate_authorities": + ConfigDomainCACertificates.log_changes(current, new_value) _changes.add_change( "edit-configvar", msg, diff --git a/cmk/gui/watolib/config_domains.py b/cmk/gui/watolib/config_domains.py index 07ad76af32a..cb08a6bee0e 100644 --- a/cmk/gui/watolib/config_domains.py +++ b/cmk/gui/watolib/config_domains.py @@ -17,10 +17,6 @@ from pathlib import Path from typing import Any, NewType, Sequence -from cryptography.hazmat.primitives import hashes -from cryptography.x509 import Certificate, load_pem_x509_certificate -from cryptography.x509.oid import NameOID - from livestatus import SiteId import cmk.ccc.version as cmk_version @@ -28,10 +24,11 @@ from cmk.ccc.exceptions import MKGeneralException import cmk.utils.paths -from cmk.utils.certs import CN_TEMPLATE, RemoteSiteCertsStore +from cmk.utils.certs import CertManagementEvent, CN_TEMPLATE, RemoteSiteCertsStore from cmk.utils.config_warnings import ConfigurationWarnings from cmk.utils.encryption import raw_certificates_from_file from cmk.utils.hostaddress import HostName +from cmk.utils.log.security_event import log_security_event from cmk.gui.background_job import BackgroundJob, BackgroundProcessInterface, InitialStatusArgs from cmk.gui.config import active_config, get_default_config @@ -53,6 +50,9 @@ ) from cmk.gui.watolib.utils import liveproxyd_config_dir, multisite_dir, wato_root_dir +from cmk.crypto.certificate import Certificate, CertificatePEM +from cmk.crypto.hash import HashAlgorithm + ProcessId = NewType("ProcessId", int) @@ -307,6 +307,53 @@ def ident(cls) -> ConfigDomainName: def config_dir(self): return multisite_dir() + @staticmethod + def log_changes( + config_before: TrustedCertificateAuthorities, + config_after: TrustedCertificateAuthorities, + ) -> None: + current_certs = { + (cert := ConfigDomainCACertificates._load_cert(value)).fingerprint( + HashAlgorithm.Sha256 + ): cert + for value in config_before["trusted_cas"] + } + + new_certs = { + (cert := ConfigDomainCACertificates._load_cert(value)).fingerprint( + HashAlgorithm.Sha256 + ): cert + for value in config_after["trusted_cas"] + } + + added_certs = [ + new_certs[fingerprint] for fingerprint in new_certs if fingerprint not in current_certs + ] + removed_certs = [ + current_certs[fingerprint] + for fingerprint in current_certs + if fingerprint not in new_certs + ] + + for cert in added_certs: + log_security_event( + CertManagementEvent( + event="certificate added", + component="trusted certificate authorities", + actor=user.id, + cert=cert, + ) + ) + for cert in removed_certs: + log_security_event( + CertManagementEvent( + event="certificate removed", + component="trusted certificate authorities", + actor=user.id, + cert=cert, + ) + ) + def config_file(self, site_specific=False): if site_specific: return os.path.join(self.config_dir(), "ca-certificates_sitespecific.mk") @@ -384,12 +431,13 @@ def _load_cert(cert_str: str) -> Certificate: Here we catch these warnings and raise an exception if the serial number is negative. """ with warnings_module.catch_warnings(record=True, category=UserWarning): - cert = load_pem_x509_certificate(cert_str.encode()) + cert = Certificate.load_pem(CertificatePEM(cert_str)) + if cert.serial_number < 0: raise _NegativeSerialException( f"Certificate with a negative serial number {cert.serial_number!r}", cert.subject.rfc4514_string(), - cert.fingerprint(hashes.SHA256()).hex(), + cert.fingerprint(HashAlgorithm.Sha256).hex(), ) return cert @@ -411,11 +459,11 @@ def _remote_sites_cas(trusted_cas: Sequence[str]) -> Mapping[SiteId, Certificate site_id: cert for cert in sorted( ConfigDomainCACertificates._load_certs(trusted_cas), - key=lambda cert: cert.not_valid_after_utc, + key=lambda cert: cert.not_valid_after, ) if ( - (cns := cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)) - and (site_id := CN_TEMPLATE.extract_site(cns[0].rfc4514_string())) + (cns := cert.subject.rfc4514_string()) + and (site_id := CN_TEMPLATE.extract_site(cns)) ) } @@ -741,7 +789,7 @@ def _to_omd_config(self, settings): # pylint: disable=too-many-branches ): settings["TRACE_SEND_TARGET"] = settings["TRACE_SEND"][1]["url"] else: - raise ValueError(f"Unhandled value: {settings["TRACE_SEND"]}") + raise ValueError(f"Unhandled value: {settings['TRACE_SEND']}") settings["TRACE_SEND"] = "on" else: settings["TRACE_SEND"] = "off" diff --git a/cmk/plugins/aws/server_side_calls/aws_agent_call.py b/cmk/plugins/aws/server_side_calls/aws_agent_call.py index 0f868874620..4ca1f85816e 100644 --- a/cmk/plugins/aws/server_side_calls/aws_agent_call.py +++ b/cmk/plugins/aws/server_side_calls/aws_agent_call.py @@ -45,7 +45,7 @@ class AwsParams(BaseModel): proxy_details: ProxyDetails | None = None access: APIAccess | None = None global_services: Mapping[str, ServiceConfig] | None = None - regions_to_monitor: list[str] | None = None + regions: list[str] | None = None services: Mapping[str, ServiceConfig] | None = None piggyback_naming_convention: Literal["ip_region_instance", "private_dns_name"] overall_tags: list[Tag] | None = None @@ -140,8 +140,8 @@ def aws_arguments( args.extend(("--assume-role", "--role-arn", role_arn_id[0])) if role_arn_id[1]: args.extend(("--external-id", role_arn_id[1])) - if params.regions_to_monitor: - args.extend(("--regions", *params.regions_to_monitor)) + if params.regions: + args.extend(("--regions", *params.regions)) global_services = params.global_services or {} if global_service_args := _get_services_args(global_services): args.extend(("--global-services", *global_service_args)) diff --git a/cmk/plugins/collection/agent_based/bonding.py b/cmk/plugins/collection/agent_based/bonding.py index d7247f888a7..5de1ec15cb9 100644 --- a/cmk/plugins/collection/agent_based/bonding.py +++ b/cmk/plugins/collection/agent_based/bonding.py @@ -12,6 +12,7 @@ DEFAULT_PARAMS = { "ieee_302_3ad_agg_id_missmatch_state": 1, "expect_active": "ignore", + "expected_interfaces": {"expected_number": 2, "state": 0}, } @@ -170,6 +171,13 @@ def check_bonding( # pylint: disable=too-many-branches state=State.WARN, summary=f"Active: {active_if} (expected is {expected_active})" ) + if (number_interfaces := params.get("expected_interfaces")) is not None: + if (actual_number := len(properties["interfaces"])) < number_interfaces["expected_number"]: + yield Result( + state=State(number_interfaces["state"]), + summary=f"Unexpected number of interfaces (expected: {number_interfaces['expected_number']}, got: {actual_number})", + ) + check_plugin_bonding = CheckPlugin( name="bonding", diff --git a/cmk/plugins/collection/graphing/check_cert_certificate_remaining_validity.py b/cmk/plugins/collection/graphing/check_cert_certificate_remaining_validity.py new file mode 100644 index 00000000000..b3683955e98 --- /dev/null +++ b/cmk/plugins/collection/graphing/check_cert_certificate_remaining_validity.py @@ -0,0 +1,24 @@ +#!/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 cmk.graphing.v1 import metrics, perfometers, Title + +UNIT_TIME = metrics.Unit(metrics.TimeNotation()) + +metric_certificate_remaining_validity = metrics.Metric( + name="certificate_remaining_validity", + title=Title("Remaining certificate validity time"), + unit=UNIT_TIME, + color=metrics.Color.YELLOW, +) + +perfometer_certificate_remaining_validity = perfometers.Perfometer( + name="certificate_remaining_validity", + focus_range=perfometers.FocusRange( + perfometers.Closed(0), + perfometers.Open(15552000), # 180 days + ), + segments=["certificate_remaining_validity"], +) diff --git a/cmk/plugins/collection/graphing/check_cert_overall_response_time.py b/cmk/plugins/collection/graphing/check_cert_overall_response_time.py new file mode 100644 index 00000000000..05d58cbc95d --- /dev/null +++ b/cmk/plugins/collection/graphing/check_cert_overall_response_time.py @@ -0,0 +1,15 @@ +#!/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 cmk.graphing.v1 import metrics, Title + +UNIT_TIME = metrics.Unit(metrics.TimeNotation()) + +metric_overall_response_time = metrics.Metric( + name="overall_response_time", + title=Title("Overall response time"), + unit=UNIT_TIME, + color=metrics.Color.ORANGE, +) diff --git a/cmk/plugins/collection/rulesets/bonding.py b/cmk/plugins/collection/rulesets/bonding.py new file mode 100644 index 00000000000..7d000631193 --- /dev/null +++ b/cmk/plugins/collection/rulesets/bonding.py @@ -0,0 +1,153 @@ +#!/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.rulesets.v1 import Help, Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + Integer, + ServiceState, + SingleChoice, + SingleChoiceElement, +) +from cmk.rulesets.v1.form_specs.validators import NumberInRange +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + + +def get_common_elements() -> dict: + return { + "expect_active": DictElement( + parameter_form=SingleChoice( + title=Title("Warn on unexpected active interface"), + elements=[ + SingleChoiceElement("ignore", title=Title("ignore which one is active")), + SingleChoiceElement( + "primary", title=Title("require primary interface to be active") + ), + SingleChoiceElement( + "lowest", title=Title("require interface that sorts lowest alphabetically") + ), + ], + prefill=DefaultValue("ignore"), + ), + ), + "ieee_302_3ad_agg_id_missmatch_state": DictElement( + parameter_form=ServiceState( + title=Title("State for mismatching Aggregator IDs for LACP"), + prefill=DefaultValue(ServiceState.WARN), + ), + ), + "expected_interfaces": DictElement( + parameter_form=Dictionary( + title=Title("Configure the number of expected interfaces"), + elements={ + "expected_number": DictElement( + required=True, + parameter_form=Integer( + title=Title("Lower limit of expected interfaces"), + prefill=DefaultValue(2), + custom_validate=(NumberInRange(min_value=0),), + ), + ), + "state": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("State for unexpected number of interfaces"), + prefill=DefaultValue(ServiceState.OK), + ), + ), + }, + ), + ), + } + + +def _make_lnx_parameter_form() -> Dictionary: + return Dictionary( + title=Title("Linux bonding"), + elements={ + **get_common_elements(), + "bonding_mode_states": DictElement( + parameter_form=Dictionary( + title=Title("State for specific bonding modes"), + elements={ + "mode_0": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("balance-rr"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_1": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("active-backup"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_2": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("balance-xor"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_3": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("broadcast"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_4": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("802.3ad"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_5": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("balance-tlb"), prefill=DefaultValue(ServiceState.OK) + ), + ), + "mode_6": DictElement( + required=True, + parameter_form=ServiceState( + title=Title("balance-alb"), prefill=DefaultValue(ServiceState.OK) + ), + ), + }, + help_text=Help( + "Specify the monitoring state when the bonding mode is not as expected." + ), + ), + ), + }, + ignored_elements=("primary",), + ) + + +def _make_ovs_parameter_form() -> Dictionary: + return Dictionary( + title=Title("OVS bonding"), + elements=get_common_elements(), + ignored_elements=("primary",), + ) + + +rule_spec_lnx_bonding = CheckParameters( + name="bonding", + title=Title("Linux bonding interface status"), + topic=Topic.NETWORKING, + parameter_form=_make_lnx_parameter_form, + condition=HostAndItemCondition(item_title=Title("Name of the bonding interface")), +) + +rule_spec_ovs_bonding = CheckParameters( + name="ovs_bonding", + title=Title("Linux bonding interface status"), + topic=Topic.NETWORKING, + parameter_form=_make_ovs_parameter_form, + condition=HostAndItemCondition(item_title=Title("Name of the bonding interface")), +) diff --git a/cmk/plugins/collection/rulesets/httpv2.py b/cmk/plugins/collection/rulesets/httpv2.py index f85502fa660..d34b266c9b1 100644 --- a/cmk/plugins/collection/rulesets/httpv2.py +++ b/cmk/plugins/collection/rulesets/httpv2.py @@ -212,7 +212,7 @@ def _valuespec_expected_regex_body() -> Dictionary: required=True, ), "invert": DictElement( - parameter_form=BooleanChoice(label=Label("return CRITICAL if found, OK if not")), + parameter_form=BooleanChoice(label=Label("Return WARNING if found, OK if not")), required=True, ), }, diff --git a/cmk/update_config/lib.py b/cmk/update_config/lib.py index b5588df6352..1df28c1b835 100644 --- a/cmk/update_config/lib.py +++ b/cmk/update_config/lib.py @@ -3,6 +3,8 @@ # 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 import tty + def format_warning(msg: str) -> str: - return f"\033[93m {msg}\033[00m" + return f"{tty.yellow} {msg}{tty.normal}" diff --git a/cmk/utils/certs.py b/cmk/utils/certs.py index bbf3309d936..1ab01f1d7cf 100644 --- a/cmk/utils/certs.py +++ b/cmk/utils/certs.py @@ -22,7 +22,7 @@ from cmk.utils.log.security_event import SecurityEvent from cmk.utils.user import UserId -from cmk.crypto.certificate import Certificate, CertificateWithPrivateKey +from cmk.crypto.certificate import Certificate, CertificatePEM, CertificateWithPrivateKey from cmk.crypto.hash import HashAlgorithm from cmk.crypto.keys import is_supported_private_key_type, PrivateKey @@ -32,7 +32,7 @@ class _CNTemplate: def __init__(self, template: str) -> None: self._temp = template - self._match = re.compile("CN=" + template % "([^=+,]*)").match + self._match = re.compile("CN=" + template % "([^=+,]*)").search def format(self, site: SiteId | str) -> str: return self._temp % site @@ -159,17 +159,15 @@ def _set_certfile_permissions( class RemoteSiteCertsStore: - # TODO: don't expose cryptography x509.Certificate in interface - def __init__(self, path: Path) -> None: self.path: Final = path - def save(self, site_id: SiteId, cert: x509.Certificate) -> None: + def save(self, site_id: SiteId, cert: Certificate) -> None: self.path.mkdir(parents=True, exist_ok=True) - self._make_file_name(site_id).write_bytes(Certificate(cert).dump_pem().bytes) + self._make_file_name(site_id).write_bytes(cert.dump_pem().bytes) - def load(self, site_id: SiteId) -> x509.Certificate: - return x509.load_pem_x509_certificate(self._make_file_name(site_id).read_bytes()) + def load(self, site_id: SiteId) -> Certificate: + return Certificate.load_pem(CertificatePEM(self._make_file_name(site_id).read_bytes())) def _make_file_name(self, site_id: SiteId) -> Path: return self.path / f"{site_id}.pem" @@ -179,12 +177,23 @@ def _make_file_name(self, site_id: SiteId) -> Path: class CertManagementEvent(SecurityEvent): """Indicates a certificate has been added or removed""" - ComponentType = Literal["saml", "agent controller", "backup encryption keys", "agent bakery"] + ComponentType = Literal[ + "saml", + "agent controller", + "backup encryption keys", + "agent bakery", + "trusted certificate authorities", + ] def __init__( self, *, - event: Literal["certificate created", "certificate removed", "certificate uploaded"], + event: Literal[ + "certificate created", + "certificate removed", + "certificate uploaded", + "certificate added", + ], component: CertManagementEvent.ComponentType, actor: UserId | str | None, cert: Certificate | None, diff --git a/cmk/utils/structured_data.py b/cmk/utils/structured_data.py index 0a2337331f1..16baeddc440 100644 --- a/cmk/utils/structured_data.py +++ b/cmk/utils/structured_data.py @@ -234,9 +234,9 @@ def _make_row_ident(key_columns: Sequence[SDKey], row: Mapping[SDKey, SDValue]) @dataclass(frozen=True, kw_only=True) class _DictKeys(Generic[_T]): - only_old: set[_T] + only_left: set[_T] both: set[_T] - only_new: set[_T] + only_right: set[_T] @classmethod def compare(cls, *, left: set[_T], right: set[_T]) -> Self: @@ -247,9 +247,9 @@ def compare(cls, *, left: set[_T], right: set[_T]) -> Self: - relative complement of left in right """ return cls( - only_old=left - right, + only_left=left - right, both=left.intersection(right), - only_new=right - left, + only_right=right - left, ) @@ -453,11 +453,11 @@ def update( pairs: dict[SDKey, SDValue] = {} retentions: dict[SDKey, RetentionInterval] = {} - for key in compared_keys.only_old: + for key in compared_keys.only_left: pairs.setdefault(key, other.pairs[key]) retentions[key] = RetentionInterval.from_previous(other.retentions[key]) - for key in compared_keys.both.union(compared_keys.only_new): + for key in compared_keys.both.union(compared_keys.only_right): retentions[key] = retention_interval if pairs: @@ -507,10 +507,10 @@ def __eq__(self, other: object) -> bool: right=set(other.rows_by_ident), ) - if compared_row_idents.only_old: + if compared_row_idents.only_left: return False - if compared_row_idents.only_new: + if compared_row_idents.only_right: return False return all( @@ -566,7 +566,7 @@ def update( # pylint: disable=too-many-branches ) retentions: dict[SDRowIdent, dict[SDKey, RetentionInterval]] = {} - for ident in compared_row_idents.only_old: + for ident in compared_row_idents.only_left: old_row: dict[SDKey, SDValue] = {} for key, value in old_filtered_rows[ident].items(): old_row.setdefault(key, value) @@ -586,13 +586,13 @@ def update( # pylint: disable=too-many-branches right=set(self_filtered_rows[ident]), ) row: dict[SDKey, SDValue] = {} - for key in compared_keys.only_old: + for key in compared_keys.only_left: row.setdefault(key, other.rows_by_ident[ident][key]) retentions.setdefault(ident, {})[key] = RetentionInterval.from_previous( other.retentions[ident][key] ) - for key in compared_keys.both.union(compared_keys.only_new): + for key in compared_keys.both.union(compared_keys.only_right): retentions.setdefault(ident, {})[key] = retention_interval if row: @@ -606,7 +606,7 @@ def update( # pylint: disable=too-many-branches self._add_row(ident, row) update_result.add_row_reason(path, ident, "Added row", row) - for ident in compared_row_idents.only_new: + for ident in compared_row_idents.only_right: for key in self_filtered_rows[ident]: retentions.setdefault(ident, {})[key] = retention_interval @@ -676,10 +676,10 @@ def __eq__(self, other: object) -> bool: right=set(other.nodes_by_name), ) - if any(self.nodes_by_name[n] for n in compared_node_names.only_old): + if any(self.nodes_by_name[n] for n in compared_node_names.only_left): return False - if any(other.nodes_by_name[n] for n in compared_node_names.only_new): + if any(other.nodes_by_name[n] for n in compared_node_names.only_right): return False return all( @@ -927,13 +927,13 @@ def _merge_tables_by_same_or_empty_key_columns( key_columns: Sequence[SDKey], left: ImmutableTable, right: ImmutableTable ) -> ImmutableTable: compared_row_idents = _DictKeys.compare( - left=set(right.rows_by_ident), - right=set(left.rows_by_ident), + left=set(left.rows_by_ident), + right=set(right.rows_by_ident), ) rows_by_ident: dict[SDRowIdent, Mapping[SDKey, SDValue]] = {} - for ident in compared_row_idents.only_old: - rows_by_ident.setdefault(ident, right.rows_by_ident[ident]) + for ident in compared_row_idents.only_left: + rows_by_ident.setdefault(ident, left.rows_by_ident[ident]) for ident in compared_row_idents.both: rows_by_ident.setdefault( @@ -944,8 +944,8 @@ def _merge_tables_by_same_or_empty_key_columns( }, ) - for ident in compared_row_idents.only_new: - rows_by_ident.setdefault(ident, left.rows_by_ident[ident]) + for ident in compared_row_idents.only_right: + rows_by_ident.setdefault(ident, right.rows_by_ident[ident]) return ImmutableTable( key_columns=key_columns, @@ -979,21 +979,21 @@ def _merge_tables(left: ImmutableTable, right: ImmutableTable) -> ImmutableTable def _merge_nodes(left: ImmutableTree, right: ImmutableTree) -> ImmutableTree: compared_node_names = _DictKeys.compare( - left=set(right.nodes_by_name), - right=set(left.nodes_by_name), + left=set(left.nodes_by_name), + right=set(right.nodes_by_name), ) nodes_by_name: dict[SDNodeName, ImmutableTree] = {} - for name in compared_node_names.only_old: - nodes_by_name[name] = right.nodes_by_name[name] + for name in compared_node_names.only_left: + nodes_by_name[name] = left.nodes_by_name[name] for name in compared_node_names.both: nodes_by_name[name] = _merge_nodes( left=left.nodes_by_name[name], right=right.nodes_by_name[name] ) - for name in compared_node_names.only_new: - nodes_by_name[name] = left.nodes_by_name[name] + for name in compared_node_names.only_right: + nodes_by_name[name] = right.nodes_by_name[name] return ImmutableTree( path=left.path, @@ -1038,12 +1038,12 @@ def compare( elif keep_identical: compared_dict.setdefault(key, SDDeltaValue(old_value, old_value)) - compared_dict |= {k: _encode_as_new(right[k]) for k in compared_keys.only_new} - compared_dict |= {k: _encode_as_removed(left[k]) for k in compared_keys.only_old} + compared_dict |= {k: _encode_as_removed(right[k]) for k in compared_keys.only_right} + compared_dict |= {k: _encode_as_new(left[k]) for k in compared_keys.only_left} return cls( result=compared_dict, - has_changes=bool(has_changes or compared_keys.only_new or compared_keys.only_old), + has_changes=bool(has_changes or compared_keys.only_right or compared_keys.only_left), ) @@ -1052,8 +1052,8 @@ def _compare_attributes( ) -> ImmutableDeltaAttributes: return ImmutableDeltaAttributes( pairs=_DeltaDict.compare( - left=right.pairs, - right=left.pairs, + left=left.pairs, + right=right.pairs, keep_identical=False, ).result, ) @@ -1061,14 +1061,14 @@ def _compare_attributes( def _compare_tables(left: ImmutableTable, right: ImmutableTable) -> ImmutableDeltaTable: compared_row_idents = _DictKeys.compare( - left=set(right.rows_by_ident), - right=set(left.rows_by_ident), + left=set(left.rows_by_ident), + right=set(right.rows_by_ident), ) rows: list[Mapping[SDKey, SDDeltaValue]] = [] - for ident in compared_row_idents.only_old: - rows.append({k: _encode_as_removed(v) for k, v in right.rows_by_ident[ident].items()}) + for ident in compared_row_idents.only_left: + rows.append({k: _encode_as_new(v) for k, v in left.rows_by_ident[ident].items()}) for ident in compared_row_idents.both: # Note: Rows which have at least one change also provide all table fields. @@ -1077,15 +1077,15 @@ def _compare_tables(left: ImmutableTable, right: ImmutableTable) -> ImmutableDel # then it would be very annoying if the rest of the row is not shown. if ( compared_dict_result := _DeltaDict.compare( - left=right.rows_by_ident[ident], - right=left.rows_by_ident[ident], + left=left.rows_by_ident[ident], + right=right.rows_by_ident[ident], keep_identical=True, ) ).has_changes: rows.append(compared_dict_result.result) - for ident in compared_row_idents.only_new: - rows.append({k: _encode_as_new(v) for k, v in left.rows_by_ident[ident].items()}) + for ident in compared_row_idents.only_right: + rows.append({k: _encode_as_removed(v) for k, v in right.rows_by_ident[ident].items()}) return ImmutableDeltaTable( key_columns=sorted(set(left.key_columns).union(right.key_columns)), @@ -1097,11 +1097,11 @@ def _compare_trees(left: ImmutableTree, right: ImmutableTree) -> ImmutableDeltaT nodes: dict[SDNodeName, ImmutableDeltaTree] = {} compared_node_names = _DictKeys.compare( - left=set(right.nodes_by_name), - right=set(left.nodes_by_name), + left=set(left.nodes_by_name), + right=set(right.nodes_by_name), ) - for name in compared_node_names.only_new: + for name in compared_node_names.only_left: if child_left := left.nodes_by_name[name]: nodes[name] = ImmutableDeltaTree.from_tree( tree=child_left, @@ -1115,7 +1115,7 @@ def _compare_trees(left: ImmutableTree, right: ImmutableTree) -> ImmutableDeltaT if (node := _compare_trees(child_left, child_right)).get_stats(): nodes[name] = node - for name in compared_node_names.only_old: + for name in compared_node_names.only_right: if child_right := right.nodes_by_name[name]: nodes[name] = ImmutableDeltaTree.from_tree( tree=child_right, @@ -1192,10 +1192,10 @@ def __eq__(self, other: object) -> bool: right=set(other.rows_by_ident), ) - if compared_row_idents.only_old: + if compared_row_idents.only_left: return False - if compared_row_idents.only_new: + if compared_row_idents.only_right: return False return all( @@ -1288,10 +1288,10 @@ def __eq__(self, other: object) -> bool: right=set(other.nodes_by_name), ) - if any(self.nodes_by_name[n] for n in compared_node_names.only_old): + if any(self.nodes_by_name[n] for n in compared_node_names.only_left): return False - if any(other.nodes_by_name[n] for n in compared_node_names.only_new): + if any(other.nodes_by_name[n] for n in compared_node_names.only_right): return False return all( @@ -1439,13 +1439,13 @@ def _filter_delta_tree(tree: ImmutableDeltaTree, filter_tree: _FilterTree) -> Im def _compute_delta_stats(dict_: Mapping[SDKey, SDDeltaValue]) -> SDDeltaCounter: counter: SDDeltaCounter = Counter() - for value0, value1 in dict_.values(): - match [value0 is None, value1 is None]: + for left, right in dict_.values(): + match [left is None, right is None]: case [True, False]: counter["new"] += 1 case [False, True]: counter["removed"] += 1 - case [False, False] if value0 != value1: + case [False, False] if left != right: counter["changed"] += 1 return counter diff --git a/defines/dev-images/reference-image-id b/defines/dev-images/reference-image-id index 757c161e65b..db17d3fd787 100755 --- a/defines/dev-images/reference-image-id +++ b/defines/dev-images/reference-image-id @@ -34,11 +34,11 @@ fi # inline cache https://docs.docker.com/build/cache/backends/inline/ # Cache storage backends: https://docs.docker.com/build/cache/backends/ -if [ -n "${POPULATE_BUILD_CACHE}" ]; then - EXTRA_ARGS="${EXTRA_ARGS} --cache-to type=inline --push -t ${CACHE_IMAGE}" - # cannot be used currently due to 'Docker driver' but might be better in the future - # EXTRA_ARGS="${EXTRA_ARGS} --cache-to type=registry,ref="${CACHE_IMAGE}"" -fi +# if [ -n "${POPULATE_BUILD_CACHE}" ]; then +# EXTRA_ARGS="${EXTRA_ARGS} --cache-to type=inline --push -t ${CACHE_IMAGE}" +# # cannot be used currently due to 'Docker driver' but might be better in the future +# # EXTRA_ARGS="${EXTRA_ARGS} --cache-to type=registry,ref="${CACHE_IMAGE}"" +# fi if [ -n "${VERBOSE}" ]; then echo >&2 "Create reference image" @@ -58,14 +58,14 @@ else DECENT_OUTPUT_TIMEOUT=2 fi -if [ -n "${PULL_CACHE_IMAGE}" ]; then - # Pre-fetch caching image. This is not required but was reported to solve some - # issues and keeps fetching and building more transparent - "${CHECKOUT_ROOT}/scripts/decent-output" ${DECENT_OUTPUT_TIMEOUT} \ - docker pull \ - "${CACHE_IMAGE}" \ - 1>&2 || true -fi +# if [ -n "${PULL_CACHE_IMAGE}" ]; then +# # Pre-fetch caching image. This is not required but was reported to solve some +# # issues and keeps fetching and building more transparent +# "${CHECKOUT_ROOT}/scripts/decent-output" ${DECENT_OUTPUT_TIMEOUT} \ +# docker pull \ +# "${CACHE_IMAGE}" \ +# 1>&2 || true +# fi # Copy over stuff we need for the build process but is located in folders we # cannot use as build context @@ -115,7 +115,6 @@ run_optionally_exclusive "${CHECKOUT_ROOT}/scripts/decent-output" ${DECENT_OUTPU docker buildx build \ ${EXTRA_ARGS} \ --iidfile "${IIDFILE}" \ - --cache-from type=registry,ref="${CACHE_IMAGE}" \ --build-arg BASE_BUILD_IMAGE="${BASE_BUILD_IMAGE}" \ --build-context scripts="${CHECKOUT_ROOT}/buildscripts/infrastructure/build-nodes/scripts" \ --build-context dev_images="${CHECKOUT_ROOT}/defines/dev-images" \ diff --git a/omd/packages/monitoring-plugins/BUILD.monitoring-plugins.bazel b/omd/packages/monitoring-plugins/BUILD.monitoring-plugins.bazel index a042834d816..bee8ad16c55 100644 --- a/omd/packages/monitoring-plugins/BUILD.monitoring-plugins.bazel +++ b/omd/packages/monitoring-plugins/BUILD.monitoring-plugins.bazel @@ -117,21 +117,3 @@ configure_make( visibility = VISIBILITY, deps = ["@openssl"], ) - -configure_make( - name = "monitoring-plugins-root", - args = ARGS, - build_data = BUILD_DATA, - configure_command = CONFIGURE_COMMAND, - configure_in_place = CONFIGURE_IN_PLACE, - configure_options = CONFIGURE_OPTIONS, - copts = COPTS, - env = ENV, - lib_source = LIB_SOURCE, - out_data_dirs = OUT_DATA_DIRS, - out_headers_only = OUT_HEADERS_ONLY, - postfix_script = POSTFIX_SCRIPT, - targets = TARGETS, - visibility = VISIBILITY, - deps = ["@openssl//:openssl_static"], -) diff --git a/omd/packages/monitoring-plugins/monitoring-plugins.make b/omd/packages/monitoring-plugins/monitoring-plugins.make index 00ddb64df73..441f55804f1 100644 --- a/omd/packages/monitoring-plugins/monitoring-plugins.make +++ b/omd/packages/monitoring-plugins/monitoring-plugins.make @@ -1,5 +1,4 @@ MONITORING_PLUGINS := monitoring-plugins -MONITORING_PLUGINS_ROOT := $(MONITORING_PLUGINS)-root MONITORING_PLUGINS_INSTALL := $(BUILD_HELPER_DIR)/$(MONITORING_PLUGINS)-install @@ -7,7 +6,6 @@ MONITORING_PLUGINS_INSTALL := $(BUILD_HELPER_DIR)/$(MONITORING_PLUGINS)-install $(MONITORING_PLUGINS_INSTALL): # run the Bazel build process which does all the dependency stuff $(BAZEL_CMD) build @$(MONITORING_PLUGINS)//:$(MONITORING_PLUGINS) - $(BAZEL_CMD) build @$(MONITORING_PLUGINS)//:$(MONITORING_PLUGINS_ROOT) # THIS IS ALL HACKY WORKAROUND STUFF - BETTER GET RID OF IT BY LETTING # BAZEL HANDLE ALL THIS RATHER THAN MODIFYING IT! @@ -18,17 +16,9 @@ $(MONITORING_PLUGINS_INSTALL): $(eval TMP_DIR := $(shell mktemp -d)) # copy over all the plugins we built mkdir -p "$(TMP_DIR)/lib/nagios" - - # All plugins WITHOUT capabilities must link dynamically against our openssl - $(RSYNC) -r --chmod=u+w --exclude 'check_icmp' --exclude 'check_dhcp' \ + $(RSYNC) -r --chmod=u+w \ "$(BAZEL_BIN_EXT)/$(MONITORING_PLUGINS)/$(MONITORING_PLUGINS)/libexec/" \ "$(TMP_DIR)/lib/nagios/plugins/" - - # All plugins WITH capabitlies must be linked statically -> get them from the -root target - $(RSYNC) -r --chmod=u+w --include 'check_icmp' --include 'check_dhcp' \ - "$(BAZEL_BIN_EXT)/$(MONITORING_PLUGINS)/$(MONITORING_PLUGINS_ROOT)/libexec/" \ - "$(TMP_DIR)/lib/nagios/plugins/" - # copy locales and 'documentation' $(RSYNC) -r --chmod=u+w \ "$(BAZEL_BIN_EXT)/$(MONITORING_PLUGINS)/$(MONITORING_PLUGINS)/share/" \ diff --git a/omd/packages/monitoring-plugins/monitoring-plugins_http.bzl b/omd/packages/monitoring-plugins/monitoring-plugins_http.bzl index f3f0f185514..1cbb3176c82 100644 --- a/omd/packages/monitoring-plugins/monitoring-plugins_http.bzl +++ b/omd/packages/monitoring-plugins/monitoring-plugins_http.bzl @@ -2,7 +2,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("//:bazel_variables.bzl", "UPSTREAM_MIRROR_URL") def monitoring_plugins_workspace(): - version_str = "2.4.0" + version_str = "2.3.3" filename = "monitoring-plugins-" + version_str + ".tar.gz" http_archive( name = "monitoring-plugins", @@ -11,7 +11,7 @@ def monitoring_plugins_workspace(): "https://www.monitoring-plugins.org/download/" + filename, UPSTREAM_MIRROR_URL + filename, ], - sha256 = "e5dfd4ad8fde0a40da50aab3aff6d9a27020b8f283e332bc4da6ef9914f4028c", + sha256 = "7023b1dc17626c5115b061e7ce02e06f006e35af92abf473334dffe7ff3c2d6d", strip_prefix = "monitoring-plugins-" + version_str, patches = [ "//omd/packages/monitoring-plugins:patches/0001-check-icmp-allows-pl-of-101.dif", @@ -20,7 +20,6 @@ def monitoring_plugins_workspace(): "//omd/packages/monitoring-plugins:patches/0009-check_dns-case-insensitive.dif", "//omd/packages/monitoring-plugins:patches/0010-get_omd_root_in_checks.dif", "//omd/packages/monitoring-plugins:patches/0011-check_http-sanitise-http-response-body.dif", - "//omd/packages/monitoring-plugins:patches/0012-fixup-ssl-linking.diff", ], patch_args = ["-p1"], patch_tool = "patch", diff --git a/omd/packages/monitoring-plugins/patches/0001-check-icmp-allows-pl-of-101.dif b/omd/packages/monitoring-plugins/patches/0001-check-icmp-allows-pl-of-101.dif index 344d42d580c..a2ab46971df 100644 --- a/omd/packages/monitoring-plugins/patches/0001-check-icmp-allows-pl-of-101.dif +++ b/omd/packages/monitoring-plugins/patches/0001-check-icmp-allows-pl-of-101.dif @@ -1,42 +1,44 @@ -diff --git a/plugins-root/check_icmp.c b/plugins-root/check_icmp.c -index f788d428..923c2df8 100644 ---- a/plugins-root/check_icmp.c -+++ b/plugins-root/check_icmp.c -@@ -1313,7 +1313,8 @@ finish(int sig) - * conspicuously as missing entries in perfparse and cacti */ +Only in monitoring-plugins-2.0: config.log +diff -ru monitoring-plugins-2.0.orig/plugins-root/check_icmp.c monitoring-plugins-2.0/plugins-root/check_icmp.c +--- monitoring-plugins-2.0.orig/plugins-root/check_icmp.c 2014-07-06 19:55:03.000000000 +0200 ++++ monitoring-plugins-2.0/plugins-root/check_icmp.c 2014-09-11 13:58:42.693502323 +0200 +@@ -940,7 +940,9 @@ + * conspicuosly as missing entries in perfparse and cacti */ pl = 100; rta = 0; - status = STATE_CRITICAL; + if (pl >= crit.pl) + status = STATE_CRITICAL; ++ /* up the down counter if not already counted */ if(!(host->flags & FLAG_LOST_CAUSE) && targets_alive) targets_down++; - } else { -@@ -1449,7 +1450,7 @@ finish(int sig) - - + } +@@ -964,7 +966,7 @@ + host = host->next; + } /* this is inevitable */ - if(!targets_alive) status = STATE_CRITICAL; + if(!targets_alive && crit.pl <= 100) status = STATE_CRITICAL; if(min_hosts_alive > -1) { if(hosts_ok >= min_hosts_alive) status = STATE_OK; else if((hosts_ok + hosts_warn) >= min_hosts_alive) status = STATE_WARNING; -@@ -1466,7 +1467,8 @@ finish(int sig) +@@ -980,7 +982,8 @@ } i++; if(!host->icmp_recv) { - status = STATE_CRITICAL; + if (pl >= crit.pl) + status = STATE_CRITICAL; - host->rtmin=0; - host->jitter_min=0; if(host->flags & FLAG_LOST_CAUSE) { -@@ -1927,7 +1929,7 @@ get_threshold(char *str, threshold *th) + printf("%s: %s @ %s. rta nan, lost %d%%", + host->name, +@@ -1246,7 +1249,7 @@ if(!th->rta) return -1; - + if(th->rta > MAXTTL * 1000000) th->rta = MAXTTL * 1000000; - if(th->pl > 100) th->pl = 100; + if(th->pl > 101) th->pl = 101; - + return 0; } +Only in monitoring-plugins-2.0/plugins-root: check_icmp.c.orig diff --git a/omd/packages/monitoring-plugins/patches/0003-cmk-password-store.dif b/omd/packages/monitoring-plugins/patches/0003-cmk-password-store.dif index 32d818637e8..ab8c7a210c8 100644 --- a/omd/packages/monitoring-plugins/patches/0003-cmk-password-store.dif +++ b/omd/packages/monitoring-plugins/patches/0003-cmk-password-store.dif @@ -1,8 +1,6 @@ -diff --git a/plugins/Makefile.am b/plugins/Makefile.am -index 49086b7a..3994374d 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am -@@ -83,7 +83,7 @@ check_fping_LDADD = $(NETLIBS) +@@ -83,7 +83,7 @@ check_game_LDADD = $(BASEOBJS) check_http_LDADD = $(SSLOBJS) check_hpjd_LDADD = $(NETLIBS) @@ -11,11 +9,9 @@ index 49086b7a..3994374d 100644 check_load_LDADD = $(BASEOBJS) check_mrtg_LDADD = $(BASEOBJS) check_mrtgtraf_LDADD = $(BASEOBJS) -diff --git a/plugins/Makefile.in b/plugins/Makefile.in -index 49086b7a..3994374d 100644 --- a/plugins/Makefile.in +++ b/plugins/Makefile.in -@@ -2231,7 +2231,7 @@ +@@ -1613,7 +1613,7 @@ check_game_LDADD = $(BASEOBJS) check_http_LDADD = $(SSLOBJS) check_hpjd_LDADD = $(NETLIBS) @@ -24,14 +20,13 @@ index 49086b7a..3994374d 100644 check_load_LDADD = $(BASEOBJS) check_mrtg_LDADD = $(BASEOBJS) check_mrtgtraf_LDADD = $(BASEOBJS) -diff --git a/plugins/check_http.c b/plugins/check_http.c -index cdf768c9..2e1b9fbc 100644 ---- a/plugins/check_http.c -+++ b/plugins/check_http.c -@@ -153,9 +153,12 @@ void print_help (void); +diff -Nur monitoring-plugins-2.1.1.orig/plugins/check_http.c monitoring-plugins-2.1.1/plugins/check_http.c +--- monitoring-plugins-2.1.1.orig/plugins/check_http.c 2014-11-30 11:36:26.000000000 +0100 ++++ monitoring-plugins-2.1.1/plugins/check_http.c 2016-07-28 16:02:08.820778404 +0200 +@@ -145,9 +145,12 @@ + void print_help (void); void print_usage (void); - char *unchunk_content(const char *content); - + +#include "cmk_password_store.h" + int @@ -39,40 +34,40 @@ index cdf768c9..2e1b9fbc 100644 { + CMK_REPLACE_PASSWORDS; int result = STATE_UNKNOWN; - + setlocale (LC_ALL, ""); diff --git a/plugins/check_ldap.c b/plugins/check_ldap.c -index 868ffc1e..b7d0f8a1 100644 +index bc7bd44c..c6020a91 100644 --- a/plugins/check_ldap.c +++ b/plugins/check_ldap.c -@@ -79,10 +79,13 @@ bool verbose = false; - +@@ -79,10 +79,12 @@ int verbose = 0; + char *SERVICE = "LDAP"; - + +#include "cmk_password_store.h" + int main (int argc, char *argv[]) { - +- + CMK_REPLACE_PASSWORDS; LDAP *ld; LDAPMessage *result; - + diff --git a/plugins/check_smtp.c b/plugins/check_smtp.c -index 986c3e18..89c80386 100644 +index d37c57c8..cdc2ef24 100644 --- a/plugins/check_smtp.c +++ b/plugins/check_smtp.c -@@ -120,10 +120,12 @@ enum { +@@ -112,10 +112,12 @@ enum { }; - bool ignore_send_quit_failure = false; - + int ignore_send_quit_failure = FALSE; + +#include "cmk_password_store.h" - + int main (int argc, char **argv) { + CMK_REPLACE_PASSWORDS; - bool supports_tls = false; + short supports_tls=FALSE; int n = 0; double elapsed_time; diff --git a/omd/packages/monitoring-plugins/patches/0010-get_omd_root_in_checks.dif b/omd/packages/monitoring-plugins/patches/0010-get_omd_root_in_checks.dif index f4ea14d4e1e..37b16a212f5 100644 --- a/omd/packages/monitoring-plugins/patches/0010-get_omd_root_in_checks.dif +++ b/omd/packages/monitoring-plugins/patches/0010-get_omd_root_in_checks.dif @@ -1,47 +1,30 @@ -diff --git a/lib/utils_cmd.c b/lib/utils_cmd.c -index 7957ec14..a776b6ba 100644 ---- a/lib/utils_cmd.c -+++ b/lib/utils_cmd.c -@@ -387,6 +387,22 @@ cmd_file_read ( char *filename, output *out, int flags) - return 0; - } +--- a/plugins/check_snmp.c 2021-04-10 14:13:41.000000000 +0200 ++++ b/plugins/check_snmp.c 2022-04-04 15:54:22.705840165 +0200 +@@ -299,9 +299,11 @@ + snmpcmd = strdup (PATH_TO_SNMPGET); + } -+char * get_absolute_snmp_cmd_path(char* relativ_snmp_cmd) -+{ -+ // We need to dynamically get the OMD_ROOT env variable as we do not know it during compile time: -+ // We're caching the compiled monitoring-plugins and reusing it for multiple versions of checkmk -+ char *omd_root = getenv("OMD_ROOT"); -+ if (omd_root == NULL) { -+ die(STATE_UNKNOWN, _("OMD_ROOT not set")); -+ } -+ -+ char * absolute_snmpcmd_path = calloc(1, strlen(omd_root) + strlen(relativ_snmp_cmd) + 1); -+ strcat(absolute_snmpcmd_path, omd_root); -+ strcat(absolute_snmpcmd_path, relativ_snmp_cmd); -+ -+ return absolute_snmpcmd_path; -+} ++ char *absolute_snmpcmd_path= get_absolute_snmp_cmd_path(snmpcmd); + - void - timeout_alarm_handler (int signo) - { -diff --git a/lib/utils_cmd.h b/lib/utils_cmd.h -index 061f5d4f..a93ed6e6 100644 ---- a/lib/utils_cmd.h -+++ b/lib/utils_cmd.h -@@ -23,6 +23,7 @@ typedef struct output output; - int cmd_run (const char *, output *, output *, int); - int cmd_run_array (char *const *, output *, output *, int); - int cmd_file_read (char *, output *, int); -+char * get_absolute_snmp_cmd_path(char*); + /* 10 arguments to pass before context and authpriv options + 1 for host and numoids. Add one for terminating NULL */ + command_line = calloc (10 + numcontext + numauthpriv + 1 + numoids + 1, sizeof (char *)); +- command_line[0] = snmpcmd; ++ command_line[0] = absolute_snmpcmd_path; + command_line[1] = strdup ("-Le"); + command_line[2] = strdup ("-t"); + xasprintf (&command_line[3], "%d", timeout_interval); +@@ -324,7 +326,7 @@ - /* only multi-threaded plugins need to bother with this */ - void cmd_init (void); -diff --git a/plugins/check_hpjd.c b/plugins/check_hpjd.c -index c34bb082..b24ff677 100644 ---- a/plugins/check_hpjd.c -+++ b/plugins/check_hpjd.c -@@ -37,6 +37,7 @@ const char *email = "devel@monitoring-plugins.org"; + /* This is just for display purposes, so it can remain a string */ + xasprintf(&cl_hidden_auth, "%s -Le -t %d -r %d -m %s -v %s %s %s %s:%s", +- snmpcmd, timeout_interval, retries, strlen(miblist) ? miblist : "''", proto, "[context]", "[authpriv]", ++ absolute_snmpcmd_path, timeout_interval, retries, strlen(miblist) ? miblist : "''", proto, "[context]", "[authpriv]", + server_address, port); + + for (i = 0; i < numoids; i++) { +--- a/plugins/check_hpjd.c 2022-10-19 12:50:27.000000000 +0000 ++++ b/plugins/check_hpjd.c 2023-01-13 12:09:34.120943312 +0000 +@@ -37,6 +37,7 @@ #include "popen.h" #include "utils.h" #include "netutils.h" @@ -49,7 +32,7 @@ index c34bb082..b24ff677 100644 #define DEFAULT_COMMUNITY "public" #define DEFAULT_PORT "161" -@@ -121,12 +122,9 @@ main (int argc, char **argv) +@@ -121,12 +122,9 @@ HPJD_GD_DOOR_OPEN, HPJD_GD_PAPER_OUTPUT, HPJD_GD_STATUS_DISPLAY); /* get the command to run */ @@ -65,32 +48,38 @@ index c34bb082..b24ff677 100644 /* run the command */ child_process = spopen (command_line); -diff --git a/plugins/check_snmp.c b/plugins/check_snmp.c -index 295aa9b5..5fd9325f 100644 ---- a/plugins/check_snmp.c -+++ b/plugins/check_snmp.c -@@ -311,12 +311,14 @@ main (int argc, char **argv) - snmpcmd = strdup (PATH_TO_SNMPGET); - } - -+ char *absolute_snmpcmd_path= get_absolute_snmp_cmd_path(snmpcmd); -+ - /* 10 arguments to pass before context and authpriv options + 1 for host and numoids. Add one for terminating NULL */ - - unsigned index = 0; - command_line = calloc (11 + numcontext + numauthpriv + 1 + numoids + 1, sizeof (char *)); - -- command_line[index++] = snmpcmd; -+ command_line[0] = absolute_snmpcmd_path; - command_line[index++] = strdup ("-Le"); - command_line[index++] = strdup ("-t"); - xasprintf (&command_line[index++], "%d", timeout_interval); -@@ -328,7 +330,7 @@ main (int argc, char **argv) - command_line[index++] = strdup (proto); +--- a/lib/utils_cmd.h 2021-04-10 14:13:41.000000000 +0200 ++++ b/lib/utils_cmd.h 2022-04-04 15:45:58.801463952 +0200 +@@ -23,6 +23,7 @@ + int cmd_run (const char *, output *, output *, int); + int cmd_run_array (char *const *, output *, output *, int); + int cmd_file_read (char *, output *, int); ++char * get_absolute_snmp_cmd_path(char*); - xasprintf(&cl_hidden_auth, "%s -Le -t %d -r %d -m %s -v %s", -- snmpcmd, timeout_interval, retries, strlen(miblist) ? miblist : "''", proto); -+ absolute_snmpcmd_path, timeout_interval, retries, strlen(miblist) ? miblist : "''", proto); + /* only multi-threaded plugins need to bother with this */ + void cmd_init (void); +--- a/lib/utils_cmd.c 2021-04-10 14:13:41.000000000 +0200 ++++ b/lib/utils_cmd.c 2022-04-04 15:45:58.801463952 +0200 +@@ -383,6 +383,22 @@ + return 0; + } - if (ignore_mib_parsing_errors) { - command_line[index++] = "-Pe"; ++char * get_absolute_snmp_cmd_path(char* relativ_snmp_cmd) ++{ ++ // We need to dynamically get the OMD_ROOT env variable as we do not know it during compile time: ++ // We're caching the compiled monitoring-plugins and reusing it for multiple versions of checkmk ++ char *omd_root = getenv("OMD_ROOT"); ++ if (omd_root == NULL) { ++ die(STATE_UNKNOWN, _("OMD_ROOT not set")); ++ } ++ ++ char * absolute_snmpcmd_path = calloc(1, strlen(omd_root) + strlen(relativ_snmp_cmd) + 1); ++ strcat(absolute_snmpcmd_path, omd_root); ++ strcat(absolute_snmpcmd_path, relativ_snmp_cmd); ++ ++ return absolute_snmpcmd_path; ++} ++ + void + timeout_alarm_handler (int signo) + { diff --git a/omd/packages/monitoring-plugins/patches/0012-fixup-ssl-linking.diff b/omd/packages/monitoring-plugins/patches/0012-fixup-ssl-linking.diff deleted file mode 100644 index 50c207a65f3..00000000000 --- a/omd/packages/monitoring-plugins/patches/0012-fixup-ssl-linking.diff +++ /dev/null @@ -1,48 +0,0 @@ -# This patch fixes linking of some plugins against our own openssl -# In a bright future, we should completely avoid letting autotools perform any magic and have hermetic builds in bazel... ---- a/plugins/Makefile.am 2024-09-16 15:33:48.465903341 +0200 -+++ b/plugins/Makefile.am 2024-09-16 15:34:23.762761627 +0200 -@@ -89,17 +89,17 @@ - check_mrtgtraf_LDADD = $(BASEOBJS) - check_mysql_CFLAGS = $(AM_CFLAGS) $(MYSQLCFLAGS) - check_mysql_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQLINCLUDE) --check_mysql_LDADD = $(NETLIBS) $(MYSQLLIBS) -+check_mysql_LDADD = $(NETLIBS) $(MYSQLLIBS) $(SSLOBJS) - check_mysql_query_CFLAGS = $(AM_CFLAGS) $(MYSQLCFLAGS) - check_mysql_query_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQLINCLUDE) --check_mysql_query_LDADD = $(NETLIBS) $(MYSQLLIBS) -+check_mysql_query_LDADD = $(NETLIBS) $(MYSQLLIBS) $(SSLOBJS) - check_nagios_LDADD = $(BASEOBJS) - check_nt_LDADD = $(NETLIBS) - check_ntp_LDADD = $(NETLIBS) $(MATHLIBS) - check_ntp_peer_LDADD = $(NETLIBS) $(MATHLIBS) - check_nwstat_LDADD = $(NETLIBS) - check_overcr_LDADD = $(NETLIBS) --check_pgsql_LDADD = $(NETLIBS) $(PGLIBS) -+check_pgsql_LDADD = $(NETLIBS) $(PGLIBS) $(SSLOBJS) - check_ping_LDADD = $(NETLIBS) - check_procs_LDADD = $(BASEOBJS) - check_radius_LDADD = $(NETLIBS) $(RADIUSLIBS) ---- a/plugins/Makefile.in 2024-09-16 15:36:24.107267031 +0200 -+++ b/plugins/Makefile.in 2024-09-16 15:36:13.751310226 +0200 -@@ -2237,17 +2237,17 @@ - check_mrtgtraf_LDADD = $(BASEOBJS) - check_mysql_CFLAGS = $(AM_CFLAGS) $(MYSQLCFLAGS) - check_mysql_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQLINCLUDE) --check_mysql_LDADD = $(NETLIBS) $(MYSQLLIBS) -+check_mysql_LDADD = $(NETLIBS) $(MYSQLLIBS) $(SSLOBJS) - check_mysql_query_CFLAGS = $(AM_CFLAGS) $(MYSQLCFLAGS) - check_mysql_query_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQLINCLUDE) --check_mysql_query_LDADD = $(NETLIBS) $(MYSQLLIBS) -+check_mysql_query_LDADD = $(NETLIBS) $(MYSQLLIBS) $(SSLOBJS) - check_nagios_LDADD = $(BASEOBJS) - check_nt_LDADD = $(NETLIBS) - check_ntp_LDADD = $(NETLIBS) $(MATHLIBS) - check_ntp_peer_LDADD = $(NETLIBS) $(MATHLIBS) - check_nwstat_LDADD = $(NETLIBS) - check_overcr_LDADD = $(NETLIBS) --check_pgsql_LDADD = $(NETLIBS) $(PGLIBS) -+check_pgsql_LDADD = $(NETLIBS) $(PGLIBS) $(SSLOBJS) - check_ping_LDADD = $(NETLIBS) - check_procs_LDADD = $(BASEOBJS) - check_radius_LDADD = $(NETLIBS) $(RADIUSLIBS) diff --git a/omd/packages/omd/omdlib/main.py b/omd/packages/omd/omdlib/main.py index 4ba26b0f561..ef6d0f081a6 100644 --- a/omd/packages/omd/omdlib/main.py +++ b/omd/packages/omd/omdlib/main.py @@ -1868,7 +1868,7 @@ def call_scripts( if file.name[0] == ".": continue sys.stdout.write(f'Executing {phase} script "{file.name}"...') - returncode = _call_script(open_pty, env, str(file)) + returncode = _call_script(open_pty, env, [str(file)]) if not returncode: sys.stdout.write(tty.ok + "\n") @@ -1877,48 +1877,41 @@ def call_scripts( raise SystemExit(1) -def _call_script( # pylint: disable=too-many-branches - open_pty: bool, env: Mapping[str, str], command: str +def _call_script( + open_pty: bool, + env: Mapping[str, str], + command: Sequence[str], ) -> int: + def forward_to_stdout(text_io: IO[str]) -> None: + line = text_io.readline() + if line: + sys.stdout.write("\n") + for line in text_io: + sys.stdout.write(f"-| {line}") + if open_pty: fd_parent, fd_child = pty.openpty() - stdout = stderr = fd_child - else: - stdout = subprocess.PIPE - stderr = subprocess.STDOUT - - with subprocess.Popen( # nosec B602 # BNS:2b5952 - command, # path-like args is not allowed when shell is true - shell=True, - stdout=stdout, - stderr=stderr, - encoding="utf-8", - env=env, - ) as proc: - if open_pty: + with subprocess.Popen( + command, + stdout=fd_child, + stderr=fd_child, + encoding="utf-8", + env=env, + ) as proc: os.close(fd_child) - parent: IO[str] = os.fdopen(fd_parent, buffering=1) - else: + with open(fd_parent) as parent: + with contextlib.suppress(OSError): + forward_to_stdout(parent) + else: + with subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + env=env, + ) as proc: assert proc.stdout is not None - parent = proc.stdout - - wrote_output = False - try: - while True: - line = parent.readline() - if not line: - break - if not wrote_output: - sys.stdout.write("\n") - wrote_output = True - - sys.stdout.write(f"-| {line}") - sys.stdout.flush() - except OSError: - pass - finally: - if open_pty: - parent.close() + forward_to_stdout(proc.stdout) return proc.returncode @@ -2938,6 +2931,8 @@ def main_update( # pylint: disable=too-many-branches "OMD_TO_VERSION": to_version, "OMD_FROM_EDITION": from_edition, } + command = ["cmk-update-config", "--conflict", conflict_mode, "--dry-run"] + sys.stdout.write("Executing '{}'".format(subprocess.list2cmdline(command))) returncode = _call_script( is_tty, { @@ -2946,7 +2941,7 @@ def main_update( # pylint: disable=too-many-branches "OMD_SITE": site.name, **additional_update_env, }, - f"cmk-update-config --conflict {conflict_mode} --dry-run", + command, ) if returncode != 0: sys.exit(returncode) diff --git a/packages/check-http/src/checks.rs b/packages/check-http/src/checks.rs index 79314405f74..a8a8cd880df 100644 --- a/packages/check-http/src/checks.rs +++ b/packages/check-http/src/checks.rs @@ -351,32 +351,31 @@ fn check_body_matching(body: Option<&Body>, matcher: Vec) -> Vec ( + } => { if *expectation { - "Expected regex in body" + ("Expected regex in body", "matched", "not matched") } else { - "Not expected regex in body" - }, - "matched", - ), + ("Not expected regex in body", "not matched", "matched") + } + } TextMatcher::Contains(_) | TextMatcher::Exact(_) => { - ("Expected string in body", "found") + ("Expected string in body", "found", "not found") } }; if m.match_on(&body.text) { vec![CheckResult::details( State::Ok, - &format!("{}: {} ({})", match_text, m.inner(), predicate), + &format!("{}: {} ({})", match_text, m.inner(), match_predicate), )] } else { notice( State::Warn, - &format!("{}: {} (not {})", match_text, m.inner(), predicate), + &format!("{}: {} ({})", match_text, m.inner(), not_match_predicate), ) } }) @@ -1280,11 +1279,25 @@ mod test_check_body_matching { ), vec![CheckResult::details( State::Ok, - "Not expected regex in body: f.*z (matched)" + "Not expected regex in body: f.*z (not matched)" ),] ); } + #[test] + fn test_regex_inverse_not_ok() { + assert_eq!( + check_body_matching( + test_body("argl").as_ref(), + vec![TextMatcher::from_regex(Regex::new("argl").unwrap(), false)] + ), + vec![ + CheckResult::summary(State::Warn, "Not expected regex in body: argl (matched)"), + CheckResult::details(State::Warn, "Not expected regex in body: argl (matched)") + ] + ); + } + #[test] fn test_multiple_matchers_ok() { assert_eq!( diff --git a/packages/cmk-frontend-vue/src/form/components/FormReadonly.vue b/packages/cmk-frontend-vue/src/form/components/FormReadonly.vue index bce65c94825..50ff2f1356d 100644 --- a/packages/cmk-frontend-vue/src/form/components/FormReadonly.vue +++ b/packages/cmk-frontend-vue/src/form/components/FormReadonly.vue @@ -191,7 +191,10 @@ function renderBooleanChoice(formSpec: BooleanChoice, value: boolean): VNode { } function renderFixedValue(formSpec: FixedValue): VNode { - const shownValue = formSpec.label ? formSpec.label : formSpec.value + let shownValue = formSpec.value + if (formSpec.label != null) { + shownValue = formSpec.label + } return h('div', shownValue as string) } diff --git a/packages/cmk-frontend-vue/src/form/components/forms/FormFixedValue.vue b/packages/cmk-frontend-vue/src/form/components/forms/FormFixedValue.vue index 7fb6776aa6b..8357d280ad3 100644 --- a/packages/cmk-frontend-vue/src/form/components/forms/FormFixedValue.vue +++ b/packages/cmk-frontend-vue/src/form/components/forms/FormFixedValue.vue @@ -22,11 +22,14 @@ const [validation, _value] = useValidation( ) const fixedValue = computed(() => { - return props.spec.label || props.spec.value + if (props.spec.label != null) { + return props.spec.label + } + return props.spec.value }) diff --git a/packages/cmk-frontend-vue/src/main.ts b/packages/cmk-frontend-vue/src/main.ts index a2782e5ef3b..0e66939f2e6 100644 --- a/packages/cmk-frontend-vue/src/main.ts +++ b/packages/cmk-frontend-vue/src/main.ts @@ -3,11 +3,6 @@ * 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. */ -/** - * 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. - */ // see https://github.com/vuejs/eslint-plugin-vue/issues/2201 /* eslint-disable vue/one-component-per-file */ diff --git a/packages/cmk-frontend-vue/src/stage1.ts b/packages/cmk-frontend-vue/src/stage1.ts index 64a0bc24c27..2c09b3d9f87 100644 --- a/packages/cmk-frontend-vue/src/stage1.ts +++ b/packages/cmk-frontend-vue/src/stage1.ts @@ -3,11 +3,6 @@ * 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. */ -/** - * 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. - */ // this is the main entrypoint for loading the auto hot reload version of vue // frontend. this file will also be shipped with the regular checkmk build, as diff --git a/packages/cmk-frontend-vue/tests/form/components/forms/FormFixedValue.test.ts b/packages/cmk-frontend-vue/tests/form/components/forms/FormFixedValue.test.ts index 6d9b0b3d2fe..fd22e5671e4 100644 --- a/packages/cmk-frontend-vue/tests/form/components/forms/FormFixedValue.test.ts +++ b/packages/cmk-frontend-vue/tests/form/components/forms/FormFixedValue.test.ts @@ -8,14 +8,17 @@ import type * as FormSpec from '@/form/components/vue_formspec_components' import FormFixedValue from '@/form/components/forms/FormFixedValue.vue' function getFixedValue(withLabel = false): FormSpec.FixedValue { - return { + const spec: FormSpec.FixedValue = { type: 'fixed_value', title: 'fooTitle', help: 'fooHelp', validators: [], - label: withLabel ? 'fooLabel' : '', value: '42' } + if (withLabel) { + spec['label'] = 'fooLabel' + } + return spec } test('FormFixedValue renders value', () => { diff --git a/packages/cmk-frontend-vue/tests/quick-setup/widgets/NoteTextWidget.test.ts b/packages/cmk-frontend-vue/tests/quick-setup/widgets/NoteTextWidget.test.ts index 5144d4b96f0..4b0db37a851 100644 --- a/packages/cmk-frontend-vue/tests/quick-setup/widgets/NoteTextWidget.test.ts +++ b/packages/cmk-frontend-vue/tests/quick-setup/widgets/NoteTextWidget.test.ts @@ -3,11 +3,6 @@ * 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. */ -/** - * 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. - */ import { render, screen } from '@testing-library/vue' import NoteTextWidget from '@/quick-setup/widgets/NoteTextWidget.vue' diff --git a/packages/cmk-frontend/.f12 b/packages/cmk-frontend/.f12 index dcee1367dd2..0863cee66b1 100755 --- a/packages/cmk-frontend/.f12 +++ b/packages/cmk-frontend/.f12 @@ -20,8 +20,7 @@ echo "Copy files..." sudo rsync -ax --delete \ - --filter "protect /js/vue_min.js*" \ - --filter "protect /js/vue_stage1.*" \ + --filter "protect /cmk-frontend-vue/" \ ./dist/ \ "$ROOT/share/check_mk/web/htdocs" diff --git a/packages/cmk-frontend/src/themes/facelift/images/icon_qs_aws.svg b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_aws.svg new file mode 100644 index 00000000000..74a702d6b4c --- /dev/null +++ b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_aws.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cmk-frontend/src/themes/facelift/images/icon_qs_azure.svg b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_azure.svg new file mode 100644 index 00000000000..9a3d10ac960 --- /dev/null +++ b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_azure.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cmk-frontend/src/themes/facelift/images/icon_qs_gcp.svg b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_gcp.svg new file mode 100644 index 00000000000..0160d979786 --- /dev/null +++ b/packages/cmk-frontend/src/themes/facelift/images/icon_qs_gcp.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cmk-frontend/src/themes/facelift/scss/_wato.scss b/packages/cmk-frontend/src/themes/facelift/scss/_wato.scss index aeafe2de9c3..d67da1f6301 100644 --- a/packages/cmk-frontend/src/themes/facelift/scss/_wato.scss +++ b/packages/cmk-frontend/src/themes/facelift/scss/_wato.scss @@ -822,6 +822,36 @@ div.filter_buttons { } } +.wato div.no-config-bundles { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 100px; + font-size: $font-size-large; + text-align: center; + + img { + width: 140px; + height: 140px; + margin-bottom: 32px; + } + + p { + max-width: 280px; + line-height: 1.5; + } + + a { + text-decoration: none; + font-weight: $font-weight-bold; + color: $font-color-light-bg; + background-color: $success-dimmed; + border: 1px solid $default-button-border-color; + border-radius: $theme-border-radius; + padding: 12px 16px; + } +} + /* page "global settings*/ .wato div.globalvars { a { diff --git a/packages/mk-sql/.cargo/config.toml b/packages/mk-sql/.cargo/config.toml new file mode 100644 index 00000000000..9cf24765317 --- /dev/null +++ b/packages/mk-sql/.cargo/config.toml @@ -0,0 +1,9 @@ +# Options below are to provide static linking +[target.'cfg(all(windows, target_env = "msvc"))'] +rustflags = [ + "-C", "target-feature=+crt-static", + "-C", "link-args=/DEFAULTLIB:libucrt.lib /DEFAULTLIB:libvcruntime.lib /DEFAULTLIB:libcmt.lib /DEFAULTLIB:msvcrt.lib /DEFAULTLIB:ucrt.lib", + "-C", "link-args=/NODEFAULTLIB:libvcruntimed.lib /NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:vcruntimed.lib", + "-C", "link-args=/NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:msvcrtd.lib", + "-C", "link-args=/NODEFAULTLIB:ucrt.lib /NODEFAULTLIB:libucrtd.lib /NODEFAULTLIB:ucrtd.lib" +] \ No newline at end of file diff --git a/packages/mk-sql/.cargo/readme.md b/packages/mk-sql/.cargo/readme.md new file mode 100644 index 00000000000..bba3189bc16 --- /dev/null +++ b/packages/mk-sql/.cargo/readme.md @@ -0,0 +1,4 @@ +Static linking of windows vcruntime140.dll + +See +https://users.rust-lang.org/t/static-vcruntime-distribute-windows-msvc-binaries-without-needing-to-deploy-vcruntime-dll/57599 \ No newline at end of file diff --git a/scripts/run-in-docker.sh b/scripts/run-in-docker.sh index 444fddeeefb..f7675edd642 100755 --- a/scripts/run-in-docker.sh +++ b/scripts/run-in-docker.sh @@ -37,7 +37,7 @@ echo >&2 "run-in-docker.sh checkpoint 1" echo >&2 "run-in-docker.sh checkpoint 2 $IMAGE_ID" # TODO: Remove all operations on *.cid files as soon as we finished debugging -find "${CHECKOUT_ROOT}" -name "*.cid" -delete +find "${CHECKOUT_ROOT}" -maxdepth 1 -name "*.cid" -delete IMAGE_VERSION="$(docker run --rm -v "${CHECKOUT_ROOT}/omd:/tmp" "${IMAGE_ID}" /tmp/distro '-')" @@ -122,8 +122,8 @@ fi echo >&2 "run-in-docker.sh checkpoint 4" function cleanup { - echo >&2 "run-in-docker.sh checkpoint 5 $(cat "$CONTAINER_NAME.cid" || echo CID_FILE_NOT_FOUND)" - rm -f "$CONTAINER_NAME.cid" + echo >&2 "run-in-docker.sh checkpoint 5 $(cat "${CHECKOUT_ROOT}/${CONTAINER_NAME}.cid" 2>/dev/null || echo CID_FILE_NOT_FOUND)" + rm -f "${CHECKOUT_ROOT}/${CONTAINER_NAME}.cid" # FIXME: eventually created image is not being cleaned up diff --git a/tests/integration/cmk/base/test_automations.py b/tests/integration/cmk/base/test_automations.py index 4cfbf27c2e7..6fc94484b51 100644 --- a/tests/integration/cmk/base/test_automations.py +++ b/tests/integration/cmk/base/test_automations.py @@ -645,7 +645,7 @@ def test_automation_active_check_icmp_all_ipv4(site: Site) -> None: ) assert isinstance(result, results.ActiveCheckResult) assert result.state == 0 - assert result.output.startswith("OK - 127.0.0.1 rta") + assert result.output.startswith("OK - 127.0.0.1: rta") def test_automation_active_check_unknown_custom(site: Site) -> None: diff --git a/tests/integration/cmk/gui/test_modules.py b/tests/integration/cmk/gui/test_modules.py index 236d294ad40..b4382c3f3b9 100644 --- a/tests/integration/cmk/gui/test_modules.py +++ b/tests/integration/cmk/gui/test_modules.py @@ -43,6 +43,7 @@ def fixture_result_file(site: Site) -> Iterator[None]: site.delete_file("tmp/dashboard_test") +@pytest.mark.skip(reason="Test is flaky, should be fixed and unskipped with CMK-19250") @pytest.mark.usefixtures("plugin_path", "result_file") def test_load_dashboard_plugin_omd_restart(request: pytest.FixtureRequest, site: Site) -> None: # Reload site apache to trigger the reload of our plugin diff --git a/tests/integration/omd/test_basic_commands.py b/tests/integration/omd/test_basic_commands.py index 9b135493801..9d8103c3f44 100644 --- a/tests/integration/omd/test_basic_commands.py +++ b/tests/integration/omd/test_basic_commands.py @@ -3,6 +3,8 @@ # 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. +import pytest + from tests.testlib.site import Site @@ -22,3 +24,15 @@ def test_basic_commands(site: Site) -> None: for rel_path in commands: assert site.file_exists(rel_path) + + +@pytest.mark.parametrize( + "command", + [ + ["bc", "--help"], + ["file", "--help"], + ], +) +def test_additional_os_command_availability(site: Site, command: list[str]) -> None: + # Commands executed here should return with exit code 0 + site.check_output(command) diff --git a/tests/integration/omd/test_packages.py b/tests/integration/omd/test_packages.py index 5bdfc616b3a..680b4d506af 100644 --- a/tests/integration/omd/test_packages.py +++ b/tests/integration/omd/test_packages.py @@ -22,7 +22,7 @@ class MonitoringPlugin: binary_name: str path: str = "lib/nagios/plugins" cmd_line_option: str = "-V" - expected: str = "v2.4.0" + expected: str = "v2.3.3" @dataclass(frozen=True) diff --git a/tests/pylint/checker_cmk_module_layers.py b/tests/pylint/checker_cmk_module_layers.py index 7373ca191d1..ceb2c18ac3a 100644 --- a/tests/pylint/checker_cmk_module_layers.py +++ b/tests/pylint/checker_cmk_module_layers.py @@ -211,7 +211,7 @@ def _allowed_for_base_cee( ( _allowed_for_base(imported=imported, component=component), _in_component(imported=imported, component=Component("cmk.cee.robotmk.licensing")), - _in_component(imported=imported, component=Component("cmk.cee.robotmk.suite_log")), + _in_component(imported=imported, component=Component("cmk.cee.robotmk.html_logs")), _in_component( imported=imported, component=Component("cmk.cee.robotmk.bakery.core_bakelets") ), @@ -277,12 +277,13 @@ def _allow_for_gui_cee( _in_component(imported=imported, component=Component("cmk.cee.bakery")), _in_component(imported=imported, component=Component("cmk.cee.robotmk.views")), _in_component(imported=imported, component=Component("cmk.cee.robotmk.dashboards")), - _in_component(imported=imported, component=Component("cmk.cee.robotmk.pages")), + _in_component(imported=imported, component=Component("cmk.cee.robotmk.page_menus")), _in_component( imported=imported, component=Component("cmk.cee.robotmk.bakery.rulespecs") ), _in_component(imported=imported, component=Component("cmk.cee.robotmk.banner")), _in_component(imported=imported, component=Component("cmk.cee.robotmk.managed_robots")), + _in_component(imported=imported, component=Component("cmk.cee.robotmk.html_logs")), ) ) diff --git a/tests/unit/cmk/base/test_check_table.py b/tests/unit/cmk/base/test_check_table.py index 4a4dabd2f4e..8e5dfca040e 100644 --- a/tests/unit/cmk/base/test_check_table.py +++ b/tests/unit/cmk/base/test_check_table.py @@ -177,7 +177,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), }, @@ -192,7 +192,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: description="Unimplemented check bla_blub / ITEM", parameters=TimespecificParameters(()), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ), (CheckPluginName("blub_bla"), "ITEM"): ConfiguredService( @@ -201,7 +201,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: description="Unimplemented check blub_bla / ITEM", parameters=TimespecificParameters(), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), }, @@ -222,7 +222,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), }, @@ -242,7 +242,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ), (CheckPluginName("smart_temp"), "static-node1"): ConfiguredService( @@ -257,7 +257,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), }, @@ -278,7 +278,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), (CheckPluginName("smart_temp"), "auto-clustered"): ConfiguredService( @@ -292,7 +292,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ), }, @@ -312,7 +312,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) }, @@ -332,7 +332,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) }, @@ -352,7 +352,7 @@ def _source_of_item(table: HostCheckTable, item: str) -> str: ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) }, diff --git a/tests/unit/cmk/base/test_config.py b/tests/unit/cmk/base/test_config.py index 2b41fb897a8..0b44bdf162f 100644 --- a/tests/unit/cmk/base/test_config.py +++ b/tests/unit/cmk/base/test_config.py @@ -1428,7 +1428,7 @@ def test_host_config_custom_checks( ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), ), @@ -1446,7 +1446,7 @@ def test_host_config_custom_checks( ) ), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=True, ), ), @@ -1770,7 +1770,7 @@ def _service_list() -> list[ConfiguredService]: description="description %s" % d, parameters=TimespecificParameters(), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) for d in "FDACEB" diff --git a/tests/unit/cmk/base/test_core_config.py b/tests/unit/cmk/base/test_core_config.py index 3618d5a2109..f5e70261b19 100644 --- a/tests/unit/cmk/base/test_core_config.py +++ b/tests/unit/cmk/base/test_core_config.py @@ -225,7 +225,7 @@ def test_get_cmk_passive_service_attributes( description="CPU load", parameters=TimespecificParameters(), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) service_spec = core_config.get_cmk_passive_service_attributes( diff --git a/tests/unit/cmk/checkengine/test_autochecks.py b/tests/unit/cmk/checkengine/test_autochecks.py index 2c81b60d5d8..5d8f81f2b4b 100644 --- a/tests/unit/cmk/checkengine/test_autochecks.py +++ b/tests/unit/cmk/checkengine/test_autochecks.py @@ -15,17 +15,14 @@ import cmk.utils.paths from cmk.utils.hostaddress import HostName -from cmk.checkengine.checking import CheckPluginName, ConfiguredService +from cmk.checkengine.checking import CheckPluginName from cmk.checkengine.discovery import AutocheckEntry, AutocheckServiceWithNodes, AutochecksStore from cmk.checkengine.discovery._autochecks import _AutochecksSerializer as AutochecksSerializer from cmk.checkengine.discovery._autochecks import _consolidate_autochecks_of_real_hosts from cmk.checkengine.discovery._utils import DiscoveredItem -from cmk.checkengine.parameters import TimespecificParameters # pylint: disable=redefined-outer-name -_COMPUTED_PARAMETERS_SENTINEL = TimespecificParameters(()) - @pytest.fixture(autouse=True) def autochecks_dir(monkeypatch, tmp_path): @@ -86,14 +83,11 @@ def test_write_read(self) -> None: {'check_plugin_name': 'df', 'item': u'/', 'parameters': {}, 'service_labels': {}}, ]""", [ - ConfiguredService( + AutocheckEntry( check_plugin_name=CheckPluginName("df"), item="/", - description="df-/", # we pass a simple callback, not the real one! - parameters=_COMPUTED_PARAMETERS_SENTINEL, - discovered_parameters={}, + parameters={}, service_labels={}, - is_enforced=False, ), ], ), @@ -101,7 +95,7 @@ def test_write_read(self) -> None: ) def test_manager_get_autochecks_of( autochecks_content: str, - expected_result: Sequence[ConfiguredService], + expected_result: Sequence[AutocheckEntry], monkeypatch: pytest.MonkeyPatch, ) -> None: autochecks_file = Path(cmk.utils.paths.autochecks_dir, "host.mk") @@ -114,18 +108,8 @@ def test_manager_get_autochecks_of( manager = config_cache._autochecks_manager - result = manager.get_configured_services( - HostName("host"), - lambda *a: _COMPUTED_PARAMETERS_SENTINEL, - lambda _host, check, item: f"{check}-{item}", - lambda hostname, _desc: hostname, - ) + result = manager.get_autochecks(HostName("host")) assert result == expected_result - # see that compute_check_parameters has been called: - assert result[0].parameters is _COMPUTED_PARAMETERS_SENTINEL - - # Check that the ConfigCache method also returns the correct data - assert config_cache.get_autochecks_of(HostName("host")) == result def _entry(name: str, params: dict[str, str] | None = None) -> AutocheckEntry: diff --git a/tests/unit/cmk/checkengine/test_checking.py b/tests/unit/cmk/checkengine/test_checking.py index 55eb96e11c9..5cf6be7476b 100644 --- a/tests/unit/cmk/checkengine/test_checking.py +++ b/tests/unit/cmk/checkengine/test_checking.py @@ -3,6 +3,10 @@ # 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 dataclasses import dataclass + +from cmk.utils.hostaddress import HostAddress from cmk.utils.servicename import ServiceName from cmk.checkengine.checking import ( @@ -10,6 +14,7 @@ check_plugins_missing_data, CheckPluginName, ConfiguredService, + ServiceConfigurer, ) from cmk.checkengine.checkresults import UnsubmittableServiceCheckResult from cmk.checkengine.exitspec import ExitSpec @@ -23,7 +28,7 @@ def _service(plugin: str, item: str | None) -> ConfiguredService: description=f"test description {plugin}/{item}", parameters=TimespecificParameters(), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ) @@ -53,7 +58,7 @@ def make_aggregated_result(*, name: str, data_received: bool) -> AggregatedResul description=ServiceName("ut_service_name"), parameters=TimespecificParameters(), discovered_parameters={}, - service_labels={}, + discovered_labels={}, is_enforced=False, ), data_received=data_received, @@ -154,3 +159,39 @@ def test_missing_data_regex_and_default() -> None: (3, "not_2"), (0, "not_3"), ] + + +@dataclass +class AutocheckEntryLike: + check_plugin_name: CheckPluginName + item: str | None + parameters: Mapping[str, str] + service_labels: Mapping[str, str] + + +def test_service_configurer() -> None: + _COMPUTED_PARAMETERS_SENTINEL = TimespecificParameters(()) + + service_configurer = ServiceConfigurer( + compute_check_parameters=lambda *a: _COMPUTED_PARAMETERS_SENTINEL, + get_service_description=lambda _host, check, item: f"{check}-{item}", + get_effective_host=lambda hostname, _desc: hostname, + ) + + assert ( + result := service_configurer.configure_autochecks( + HostAddress("somehost"), [AutocheckEntryLike(CheckPluginName("df"), "/", {}, {})] + ) + ) == [ + ConfiguredService( + check_plugin_name=CheckPluginName("df"), + item="/", + description="df-/", # we pass a simple callback, not the real one! + parameters=_COMPUTED_PARAMETERS_SENTINEL, + discovered_parameters={}, + discovered_labels={}, + is_enforced=False, + ), + ] + # see that compute_check_parameters has been called: + assert result[0].parameters is _COMPUTED_PARAMETERS_SENTINEL diff --git a/tests/unit/cmk/gui/graphing/test_graph_templates.py b/tests/unit/cmk/gui/graphing/test_graph_templates.py index b4ac247f946..a0400f91e78 100644 --- a/tests/unit/cmk/gui/graphing/test_graph_templates.py +++ b/tests/unit/cmk/gui/graphing/test_graph_templates.py @@ -44,6 +44,7 @@ ) from cmk.gui.graphing._graph_templates import ( _evaluate_predictive_metrics, + _evaluate_scalars, _get_evaluated_graph_templates, _get_graph_plugins, _matching_graph_templates, @@ -55,7 +56,7 @@ GraphTemplate, MinimalGraphTemplateRange, ) -from cmk.gui.graphing._legacy import RawGraphTemplate +from cmk.gui.graphing._legacy import get_render_function, RawGraphTemplate from cmk.gui.graphing._translated_metrics import ( Original, parse_perf_data, @@ -101,7 +102,7 @@ id="1", title="Graph 1", scalars=[], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[], @@ -110,7 +111,7 @@ id="2", title="Graph 2", scalars=[], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[], @@ -247,8 +248,14 @@ def test_horizontal_rules_from_thresholds( ) -> None: perf_data, check_command = parse_perf_data(perf_data_string, None, config=active_config) translated_metrics = translate_metrics(perf_data, check_command) - assert ( - gt._horizontal_rules_from_thresholds( + assert [ + HorizontalRule( + value=e.value, + rendered_value=get_render_function(e.unit_spec)(e.value), + color=e.color, + title=e.title, + ) + for e in _evaluate_scalars( [ MetricExpression( WarningOf(Metric("one")), @@ -268,8 +275,7 @@ def test_horizontal_rules_from_thresholds( ], translated_metrics, ) - == result - ) + ] == result def test_duplicate_graph_templates(request_context: None) -> None: @@ -1313,13 +1319,13 @@ def test__evaluate_predictive_metrics_line_type( assert [ (e.base, e.line_type) for e in _evaluate_predictive_metrics( - translated_metrics, evaluate_metrics( conflicting_metrics=[], optional_metrics=[], metric_expressions=metric_expressions, translated_metrics=translated_metrics, ), + translated_metrics, ) ] == expected_predictive_metric_expressions @@ -1366,7 +1372,6 @@ def test__evaluate_predictive_metrics_duplicates() -> None: assert [ (e.base, e.line_type) for e in _evaluate_predictive_metrics( - translated_metrics, evaluate_metrics( conflicting_metrics=[], optional_metrics=[], @@ -1381,6 +1386,7 @@ def test__evaluate_predictive_metrics_duplicates() -> None: ], translated_metrics=translated_metrics, ), + translated_metrics, ) ] == [ (Metric("predict_metric_name"), "line"), @@ -1410,7 +1416,7 @@ def test__evaluate_predictive_metrics_duplicates() -> None: id="inbound_and_outbound_messages", title="Inbound and Outbound Messages", scalars=[], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[ @@ -1503,7 +1509,7 @@ def test__evaluate_predictive_metrics_duplicates() -> None: id="inbound_and_outbound_messages", title="Inbound and Outbound Messages", scalars=[], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[ @@ -1534,19 +1540,8 @@ def test__evaluate_predictive_metrics_duplicates() -> None: EvaluatedGraphTemplate( id="METRIC_foo", title="", - scalars=[ - MetricExpression( - WarningOf(metric=Metric("foo")), - line_type="line", - title="Warning", - ), - MetricExpression( - CriticalOf(metric=Metric("foo")), - line_type="line", - title="Critical", - ), - ], - consolidation_function=None, + scalars=[], + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[ @@ -1567,18 +1562,30 @@ def test__evaluate_predictive_metrics_duplicates() -> None: id="METRIC_predict_foo", title="", scalars=[ - MetricExpression( + Evaluated( WarningOf(metric=Metric("predict_foo")), - line_type="line", - title="Warning", + 1.0, + ConvertibleUnitSpecification( + notation=DecimalNotation(symbol=""), + precision=AutoPrecision(digits=2), + ), + "#ffd000", + "line", + "Warning", ), - MetricExpression( + Evaluated( CriticalOf(metric=Metric("predict_foo")), - line_type="line", - title="Critical", + 2.0, + ConvertibleUnitSpecification( + notation=DecimalNotation(symbol=""), + precision=AutoPrecision(digits=2), + ), + "#ff3232", + "line", + "Critical", ), ], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[ @@ -1599,18 +1606,30 @@ def test__evaluate_predictive_metrics_duplicates() -> None: id="METRIC_predict_lower_foo", title="", scalars=[ - MetricExpression( + Evaluated( WarningOf(metric=Metric("predict_lower_foo")), - line_type="line", - title="Warning", + 3.0, + ConvertibleUnitSpecification( + notation=DecimalNotation(symbol=""), + precision=AutoPrecision(digits=2), + ), + "#ffd000", + "line", + "Warning", ), - MetricExpression( + Evaluated( CriticalOf(metric=Metric("predict_lower_foo")), - line_type="line", - title="Critical", + 4.0, + ConvertibleUnitSpecification( + notation=DecimalNotation(symbol=""), + precision=AutoPrecision(digits=2), + ), + "#ff3232", + "line", + "Critical", ), ], - consolidation_function=None, + consolidation_function="max", range=None, omit_zero_metrics=False, metrics=[ @@ -1642,11 +1661,8 @@ def test__get_evaluated_graph_templates_with_predictive_metrics( ) -> None: perfdata: Perfdata = ( [PerfDataTuple(n, n, 0, "", None, None, None, None) for n in metric_names] - + [PerfDataTuple(n, n[8:], 0, "", None, None, None, None) for n in predict_metric_names] - + [ - PerfDataTuple(n, n[14:], 0, "", None, None, None, None) - for n in predict_lower_metric_names - ] + + [PerfDataTuple(n, n[8:], 0, "", 1, 2, None, None) for n in predict_metric_names] + + [PerfDataTuple(n, n[14:], 0, "", 3, 4, None, None) for n in predict_lower_metric_names] ) translated_metrics = translate_metrics(perfdata, check_command) assert list(_get_evaluated_graph_templates(translated_metrics)) == graph_templates diff --git a/tests/unit/cmk/gui/quick_setup/test_aws_params.py b/tests/unit/cmk/gui/quick_setup/test_aws_params.py index a5be148322d..2cae6c8973c 100644 --- a/tests/unit/cmk/gui/quick_setup/test_aws_params.py +++ b/tests/unit/cmk/gui/quick_setup/test_aws_params.py @@ -57,7 +57,8 @@ "host_assignment": "aws_host", }, }, - "regions_to_monitor": ["eu-central-1"], + "access": {}, + "regions": ["eu-central-1"], "services": { "ec2": {"selection": "all", "limits": True}, "ebs": {"selection": "all", "limits": True}, diff --git a/tests/unit/cmk/gui/quick_setup/test_quick_setup_param_collection.py b/tests/unit/cmk/gui/quick_setup/test_quick_setup_param_collection.py index baf417e4f64..582c3d21604 100644 --- a/tests/unit/cmk/gui/quick_setup/test_quick_setup_param_collection.py +++ b/tests/unit/cmk/gui/quick_setup/test_quick_setup_param_collection.py @@ -12,6 +12,7 @@ from cmk.base.server_side_calls import load_special_agents +from cmk.gui.quick_setup.config_setups.aws.form_specs import quick_setup_aws_form_spec from cmk.gui.quick_setup.v0_unstable.predefined import ( collect_params_from_form_data, collect_params_with_defaults_from_form_data, @@ -122,14 +123,15 @@ def test_quick_setup_collect_params_from_form_data() -> None: load_special_agents() assert ( - collect_params_from_form_data(ALL_FORM_SPEC_DATA, "special_agents:aws") == EXPECTED_PARAMS + collect_params_from_form_data(ALL_FORM_SPEC_DATA, quick_setup_aws_form_spec()) + == EXPECTED_PARAMS ) def test_quick_setup_collect_passwords_from_form_data() -> None: load_special_agents() assert ( - collect_passwords_from_form_data(ALL_FORM_SPEC_DATA, "special_agents:aws") + collect_passwords_from_form_data(ALL_FORM_SPEC_DATA, quick_setup_aws_form_spec()) == EXPECTED_PASSWORDS ) @@ -141,6 +143,8 @@ def test_quick_setup_collect_params_with_defaults_from_form_data( load_special_agents() with UserContext(with_user[0]): assert ( - collect_params_with_defaults_from_form_data(ALL_FORM_SPEC_DATA, "special_agents:aws") + collect_params_with_defaults_from_form_data( + ALL_FORM_SPEC_DATA, quick_setup_aws_form_spec() + ) == EXPECTED_PARAMS_WITH_DEFAULTS ) diff --git a/tests/unit/cmk/gui/test_pages.py b/tests/unit/cmk/gui/test_pages.py index 7770da7f1be..c1843ddd32c 100644 --- a/tests/unit/cmk/gui/test_pages.py +++ b/tests/unit/cmk/gui/test_pages.py @@ -228,6 +228,7 @@ def test_registered_pages() -> None: "robotmk_suite_log", "robotmk_suite_report", "download_robotmk_suite_report", + "robotmk_managed_robots", ] if cmk_version.edition(paths.omd_root) is cmk_version.Edition.CSE: diff --git a/tests/unit/cmk/gui/views/icon/test_icons.py b/tests/unit/cmk/gui/views/icon/test_icons.py index 804e21cf9d2..ef759d53455 100644 --- a/tests/unit/cmk/gui/views/icon/test_icons.py +++ b/tests/unit/cmk/gui/views/icon/test_icons.py @@ -59,7 +59,7 @@ def test_builtin_icons_and_actions() -> None: "deployment_status", "status_shadow", "ntop_host", - "robotmk_suite_ok_log", + "robotmk_html_log", ] cmk.gui.views.register_legacy_icons() diff --git a/tests/unit/cmk/gui/watolib/test_config_domains.py b/tests/unit/cmk/gui/watolib/test_config_domains.py index dc1e54bccfe..c7b3eeb30e9 100644 --- a/tests/unit/cmk/gui/watolib/test_config_domains.py +++ b/tests/unit/cmk/gui/watolib/test_config_domains.py @@ -238,10 +238,10 @@ def test_remote_sites_cas(self) -> None: ) assert list(remote_cas) == [SiteId("heute_remote_1"), SiteId("heute_remote_2")] - assert remote_cas[SiteId("heute_remote_1")].not_valid_after_utc == longest_validity + assert remote_cas[SiteId("heute_remote_1")].not_valid_after == longest_validity # also test changed order: remote_cas = ConfigDomainCACertificates()._remote_sites_cas([remote1_older, remote1_newer]) - assert remote_cas[SiteId("heute_remote_1")].not_valid_after_utc == longest_validity + assert remote_cas[SiteId("heute_remote_1")].not_valid_after == longest_validity def test_remote_root_ca_in_remote_site_cas( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch diff --git a/tests/unit/cmk/plugins/aws/server_side_calls/test_aws.py b/tests/unit/cmk/plugins/aws/server_side_calls/test_aws.py index 675729697d8..721d736056f 100644 --- a/tests/unit/cmk/plugins/aws/server_side_calls/test_aws.py +++ b/tests/unit/cmk/plugins/aws/server_side_calls/test_aws.py @@ -43,7 +43,7 @@ "host_assignment": "domain_host", }, }, - "regions_to_monitor": ["ap-northeast-2", "ap-southeast-2"], + "regions": ["ap-northeast-2", "ap-southeast-2"], "services": { "ec2": { "selection": ( diff --git a/tests/unit/cmk/plugins/collection/agent_based/test_bonding.py b/tests/unit/cmk/plugins/collection/agent_based/test_bonding.py index 93c4a9371d3..8d84fe97c22 100644 --- a/tests/unit/cmk/plugins/collection/agent_based/test_bonding.py +++ b/tests/unit/cmk/plugins/collection/agent_based/test_bonding.py @@ -4,13 +4,14 @@ # conditions defined in the file COPYING, which is part of this source code package. from collections.abc import Mapping +from typing import Any import pytest from cmk.agent_based.v1 import Result, State from cmk.agent_based.v1.type_defs import CheckResult -from cmk.plugins.collection.agent_based.bonding import _check_ieee_302_3ad_specific -from cmk.plugins.lib.bonding import Bond +from cmk.plugins.collection.agent_based.bonding import _check_ieee_302_3ad_specific, check_bonding +from cmk.plugins.lib.bonding import Bond, Interface @pytest.mark.parametrize( @@ -84,3 +85,109 @@ def test_check_ieee_302_3ad_specific( params: Mapping[str, object], status: Bond, result: CheckResult ) -> None: assert list(_check_ieee_302_3ad_specific(params, status)) == result + + +@pytest.mark.parametrize( + "params, expected_result", + [ + pytest.param( + { + "ieee_302_3ad_agg_id_missmatch_state": 1, + "expect_active": "ignore", + }, + [ + Result(state=State.OK, summary="Status: up"), + Result(state=State.OK, summary="Mode: fault-tolerance"), + Result(state=State.OK, summary="eth2/f8:4f:57:72:11:34 up"), + Result(state=State.WARN, summary="eth3/f8:4f:57:72:11:36 down"), + ], + id="No parameter for number of interfaces", + ), + pytest.param( + { + "ieee_302_3ad_agg_id_missmatch_state": 1, + "expect_active": "ignore", + "expected_interfaces": {"expected_number": 3, "state": 2}, + }, + [ + Result(state=State.OK, summary="Status: up"), + Result(state=State.OK, summary="Mode: fault-tolerance"), + Result(state=State.OK, summary="eth2/f8:4f:57:72:11:34 up"), + Result(state=State.WARN, summary="eth3/f8:4f:57:72:11:36 down"), + Result( + state=State.CRIT, + summary="Unexpected number of interfaces (expected: 3, got: 2)", + ), + ], + id="Not enough interfaces with CRIT state", + ), + pytest.param( + { + "ieee_302_3ad_agg_id_missmatch_state": 1, + "expect_active": "ignore", + "expected_interfaces": {"expected_number": 3, "state": 1}, + }, + [ + Result(state=State.OK, summary="Status: up"), + Result(state=State.OK, summary="Mode: fault-tolerance"), + Result(state=State.OK, summary="eth2/f8:4f:57:72:11:34 up"), + Result(state=State.WARN, summary="eth3/f8:4f:57:72:11:36 down"), + Result( + state=State.WARN, + summary="Unexpected number of interfaces (expected: 3, got: 2)", + ), + ], + id="Not enough interfaces with WARN state", + ), + pytest.param( + { + "ieee_302_3ad_agg_id_missmatch_state": 1, + "expect_active": "ignore", + "expected_interfaces": {"expected_number": 2, "state": 1}, + }, + [ + Result(state=State.OK, summary="Status: up"), + Result(state=State.OK, summary="Mode: fault-tolerance"), + Result(state=State.OK, summary="eth2/f8:4f:57:72:11:34 up"), + Result(state=State.WARN, summary="eth3/f8:4f:57:72:11:36 down"), + ], + id="Expected number of interfaces is equal to the actual number", + ), + pytest.param( + { + "ieee_302_3ad_agg_id_missmatch_state": 1, + "expect_active": "ignore", + "expected_interfaces": {"expected_number": 1, "state": 1}, + }, + [ + Result(state=State.OK, summary="Status: up"), + Result(state=State.OK, summary="Mode: fault-tolerance"), + Result(state=State.OK, summary="eth2/f8:4f:57:72:11:34 up"), + Result(state=State.WARN, summary="eth3/f8:4f:57:72:11:36 down"), + ], + id="Number of interfaces is bigger than expected", + ), + ], +) +def test_check_interfaces_number(params: Mapping[str, Any], expected_result: list[Result]) -> None: + input_section = { + "bond0": Bond( + status="up", + mode="fault-tolerance", + interfaces={ + "eth2": Interface(status="up", hwaddr="f8:4f:57:72:11:34", failures=1), + "eth3": Interface(status="down", hwaddr="f8:4f:57:72:11:36", failures=0), + }, + active="eth2", + primary="None", + ) + } + + result = list( + check_bonding( + "bond0", + params, + input_section, + ) + ) + assert result == expected_result diff --git a/tests/unit/cmk/plugins/collection/agent_based/test_check_plugin_properties.py b/tests/unit/cmk/plugins/collection/agent_based/test_check_plugin_properties.py index 08e0ef88caf..5815ebd4cf9 100644 --- a/tests/unit/cmk/plugins/collection/agent_based/test_check_plugin_properties.py +++ b/tests/unit/cmk/plugins/collection/agent_based/test_check_plugin_properties.py @@ -63,7 +63,10 @@ def test_check_plugins_do_not_discover_upon_empty_snmp_input(fix_register: FixRe plugins_discovering_upon_empty = set() for _name, plugin in sorted(fix_register.check_plugins.items()): for sections in _section_permutations(plugin.sections): - kwargs = {str(section.name): _get_empty_parsed_result(section) for section in sections} + kwargs = { + str(section.parsed_section_name): _get_empty_parsed_result(section) + for section in sections + } if all(v is None for v in kwargs.values()): continue diff --git a/tests/unit/cmk/plugins/collection/agent_based/test_heartbeat_crm.py b/tests/unit/cmk/plugins/collection/agent_based/test_heartbeat_crm.py index b1c0a46a98d..97b7f31b13f 100644 --- a/tests/unit/cmk/plugins/collection/agent_based/test_heartbeat_crm.py +++ b/tests/unit/cmk/plugins/collection/agent_based/test_heartbeat_crm.py @@ -220,6 +220,26 @@ def _get_section_3() -> Section: return section +@pytest.fixture(name="section_no_cluster", scope="module") +def _get_section_no_cluster() -> Section: + section = parse_heartbeat_crm([["Error: cluster is not available on this node"]]) + assert section + return section + + +@pytest.fixture(name="section_connection_refused", scope="module") +def _get_section_connection_refused() -> Section: + section = parse_heartbeat_crm( + [ + [ + "error: Could not connect to launcher: Connection refused crm_mon: Connection to cluster failed: Connection refused" + ] + ] + ) + assert section + return section + + def test_discover_heartbeat_crm(section_1: Section) -> None: assert list(discover_heartbeat_crm({"naildown_dc": False}, section_1)) == [ Service(parameters={"num_nodes": 2, "num_resources": 3}), @@ -291,6 +311,33 @@ def test_check_heartbeat_crm_crit(section_2: Section) -> None: ] +def test_check_heartbeat_crm_no_cluster_crit(section_no_cluster: Section) -> None: + assert list( + _check_heartbeat_crm( + {"dc": "hasi", "max_age": 60, "num_nodes": 1, "num_resources": 4}, + section_no_cluster, + 1559939704.5458105, + ) + ) == [ + Result(state=State.CRIT, summary="Error: cluster is not available on this node"), + ] + + +def test_check_heartbeat_crm_failed_connection_crit(section_connection_refused: Section) -> None: + assert list( + _check_heartbeat_crm( + {"dc": "hasi", "max_age": 60, "num_nodes": 1, "num_resources": 4}, + section_connection_refused, + 1559939704.5458105, + ) + ) == [ + Result( + state=State.CRIT, + summary="error: Could not connect to launcher: Connection refused crm_mon: Connection to cluster failed: Connection refused", + ), + ] + + def test_check_heartbeat_crm_resources_promotable_clone(section_3: Section) -> None: assert list( check_heartbeat_crm_resources(