Skip to content

Commit 0e29385

Browse files
oetikerclaude
andcommitted
docs: improve cmk-plugin-guide structure and fix critical errors
Major documentation improvements: - Added comprehensive table of contents for better navigation - Fixed critical typos (cmk.base.pyugins → cmk.base.plugins) that would cause import errors - Clarified SimpleLevels parameter handling to prevent common TypeError issues - Added "Common Pitfalls and Solutions" section for troubleshooting - Fixed bakery plugin directory paths for consistency - Corrected temperature monitor example to use proper HostAndItemCondition - Added important note about check_mk → python3/cmk symlink relationship - Removed redundant and contradictory SimpleLevels documentation These changes make the guide more reliable and easier to follow for developers creating CheckMK plugins. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 492e811 commit 0e29385

File tree

2 files changed

+104
-67
lines changed

2 files changed

+104
-67
lines changed

CHANGES.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- added more information and examples to cmk-plugin-guide.md
1212

1313
### Changed
14+
- Reorganized cmk-plugin-guide.md structure with table of contents and improved flow
15+
- Clarified SimpleLevels documentation to remove confusion about parameter formats
16+
- Added "Common Pitfalls and Solutions" section to cmk-plugin-guide.md
17+
- Added note about check_mk → python3/cmk symlink relationship to avoid path confusion
1418

1519
### Fixed
20+
- Fixed critical typos in cmk-plugin-guide.md (`cmk.base.pyugins``cmk.base.plugins`)
21+
- Corrected bakery plugin directory paths for consistency
22+
- Fixed missing `parameter_form=` in ruleset example code
23+
- Fixed temperature monitor example to use correct `HostAndItemCondition`
24+
- Removed redundant and contradictory information about SimpleLevels handling
1625

1726
## 2.0.2 - 2025-08-07
1827
### Fixed

cmk-plugin-guide.md

Lines changed: 95 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
66
This guide provides comprehensive instructions for developing agent-based check plugins for CheckMK 2.3.0, including Agent Bakery support, based on the official CheckMK Plugin APIs.
77

8+
## Table of Contents
9+
10+
1. [Overview](#overview)
11+
2. [Prerequisites](#prerequisites)
12+
3. [Development Environment Setup](#development-environment-setup)
13+
4. [Agent Plugin Development](#agent-plugin-development)
14+
5. [Check Plugin Development](#check-plugin-development)
15+
6. [Agent Bakery Integration](#agent-bakery-integration)
16+
7. [Ruleset Integration](#ruleset-integration)
17+
8. [Graphing Integration](#graphing-integration)
18+
9. [Best Practices](#best-practices)
19+
10. [Testing and Debugging](#testing)
20+
11. [Deployment](#deployment)
21+
12. [Advanced Topics](#advanced-topics)
22+
13. [Complete Examples](#complete-example-temperature-monitoring-plugin)
23+
14. [Troubleshooting](#debugging)
24+
825
## Overview
926

1027
CheckMK agent-based check plugins consist of two main components:
@@ -28,13 +45,15 @@ CheckMK agent-based check plugins consist of two main components:
2845
├── rulesets/ # Rule specifications (using cmk.rulesets.v1)
2946
└── server_side_calls/ # Special agents (using cmk.server_side_calls.v1)
3047
31-
~/local/lib/python3/cmk/base/cee/plugins/bakery/
32-
└── # Agent bakery plugins (using cmk.base.pyugins.bakery.bakery_api.v1)
48+
~/local/lib/check_mk/base/cee/plugins/bakery/
49+
└── # Agent bakery plugins (using cmk.base.plugins.bakery.bakery_api.v1)
3350
3451
~/local/share/check_mk/agents/plugins/
3552
└── # Agent plugin source files
3653
```
3754

55+
> **📝 Important Path Note**: In CheckMK, `~/local/lib/check_mk` is a symlink to `~/local/lib/python3/cmk`. You may encounter both paths in documentation, error messages, and examples - they refer to the same location. This can cause confusion when debugging path-related issues or following different documentation sources.
56+
3857
### Agent Plugin Location
3958
```
4059
/usr/lib/check_mk_agent/plugins/
@@ -814,60 +833,60 @@ check_levels(
814833

815834
##### SimpleLevels Format Handling
816835

817-
CheckMK 2.3 rulesets with SimpleLevels actually produce parameters directly in the `("fixed", (warn, crit))` format that `check_levels()` expects, NOT as a dictionary with 'levels_upper' key. This is a common source of confusion that can lead to TypeErrors.
836+
CheckMK 2.3 rulesets with SimpleLevels produce parameters in a specific format that's ready for `check_levels()`:
837+
838+
**Key Points:**
839+
- SimpleLevels configured in the GUI produces `("fixed", (warn, crit))` tuples directly
840+
- When no levels are configured, SimpleLevels produces `None`
841+
- The parameters are ready to use - no extraction or conversion needed
842+
- Never wrap the values in additional tuples or try to extract from non-existent dicts
818843

819844
```python
820845
def check_with_simple_levels(item: str, params: Mapping[str, Any], section: Dict[str, Any]) -> CheckResult:
821846
value = section.get("storage_usage_percent", 0)
822847

823-
# SimpleLevels from rulesets come DIRECTLY as ("fixed", (warn, crit))
824-
# NOT wrapped in a dict! This is the actual format:
825-
# params = {
826-
# 'storage_levels': ('fixed', (80.0, 90.0)), # Already in check_levels format!
827-
# 'other_param': None,
828-
# }
848+
# SimpleLevels from rulesets come as either:
849+
# - ('fixed', (80.0, 90.0)) when levels are configured
850+
# - None when no levels are set
829851

830-
# So just pass it directly to check_levels:
852+
# Just pass directly to check_levels - it handles both formats:
831853
yield from check_levels(
832854
value,
833-
levels_upper=params.get('storage_levels'), # Pass directly, no extraction needed!
855+
levels_upper=params.get('storage_levels'), # Pass directly!
834856
metric_name="storage_used_percent",
835857
label="Storage utilization",
836858
boundaries=(0.0, 100.0),
837859
render_func=render.percent,
838860
)
839861

840-
def check_with_lower_levels(item: str, params: Mapping[str, Any], section: Dict[str, Any]) -> CheckResult:
862+
def check_with_both_levels(item: str, params: Mapping[str, Any], section: Dict[str, Any]) -> CheckResult:
841863
temperature = section.get("temperature_celsius", 0)
842864

843-
# SimpleLevels parameters come directly in the correct format!
844-
# No conversion needed - just pass them to check_levels
845-
865+
# Both upper and lower levels work the same way:
846866
yield from check_levels(
847867
temperature,
848-
levels_upper=params.get('temperature_levels_upper'), # Already ("fixed", (warn, crit))
849-
levels_lower=params.get('temperature_levels_lower'), # Already ("fixed", (warn, crit))
868+
levels_upper=params.get('temperature_levels_upper'), # Direct pass
869+
levels_lower=params.get('temperature_levels_lower'), # Direct pass
850870
metric_name="temperature",
851871
label="Temperature",
852872
render_func=lambda v: f"{v:.1f}°C",
853873
)
854874
```
855875

856-
**⚠️ Common Pitfall - TypeError with SimpleLevels**
857-
858-
A frequent mistake is trying to extract levels from a dictionary structure that doesn't exist:
876+
**⚠️ Common Mistake to Avoid**
859877

860878
```python
861-
# ❌ WRONG - This causes TypeError: '>=' not supported between instances of 'float' and 'tuple'
879+
# ❌ WRONG - Don't try to extract from non-existent dict structure
862880
storage_levels = params.get('storage_levels')
863-
if storage_levels and isinstance(storage_levels, dict) and 'levels_upper' in storage_levels:
864-
levels_upper = ("fixed", storage_levels['levels_upper']) # storage_levels is already a tuple!
881+
if storage_levels and isinstance(storage_levels, dict): # This check will fail!
882+
levels = storage_levels.get('levels_upper') # storage_levels is a tuple, not a dict!
865883

866-
# ✅ CORRECT - SimpleLevels already produces the right format
867-
levels_upper = params.get('storage_levels') # Just use it directly!
884+
# ✅ CORRECT - Use the parameter directly
885+
levels = params.get('storage_levels') # Already in correct format!
868886
```
869887

870-
The TypeError occurs because the code tries to wrap `("fixed", (80.0, 90.0))` again, resulting in `("fixed", ("fixed", (80.0, 90.0)))`, which causes check_levels to fail when comparing the float value with the nested tuple.
888+
**Why This Error Happens:**
889+
The confusion often comes from older CheckMK versions or manual parameter handling patterns. In CheckMK 2.3 with SimpleLevels, the framework handles the complexity for you.
871890

872891
##### Custom Render Functions
873892

@@ -955,52 +974,29 @@ check_plugin_my_service = CheckPlugin(
955974

956975
**Note**: When SimpleLevels in the ruleset has no levels configured, it produces `None`, not `("no_levels", None)`. The check_levels function handles `None` appropriately.
957976

958-
##### Manual vs check_levels Comparison
959-
960-
**❌ Manual threshold checking (deprecated approach):**
961-
```python
962-
def check_manual_thresholds(item: str, params: Mapping[str, Any], section: Dict[str, Any]) -> CheckResult:
963-
value = section.get("metric_value", 0)
964-
965-
# Manual threshold checking - NOT recommended
966-
levels = params.get('levels')
967-
if levels and isinstance(levels, dict) and 'levels_upper' in levels:
968-
warn, crit = levels['levels_upper']
969-
if value >= crit:
970-
yield Result(state=State.CRIT, summary=f"Critical: {value} (>= {crit})")
971-
elif value >= warn:
972-
yield Result(state=State.WARN, summary=f"Warning: {value} (>= {warn})")
973-
else:
974-
yield Result(state=State.OK, summary=f"OK: {value}")
975-
976-
# Manual metric creation
977-
yield Metric("my_metric", value, levels=(warn, crit) if levels else None)
978-
```
977+
##### Best Practice: Always Use check_levels
979978

980-
**✅ Using check_levels (recommended approach):**
981979
```python
982980
def check_with_check_levels(item: str, params: Mapping[str, Any], section: Dict[str, Any]) -> CheckResult:
983981
value = section.get("metric_value", 0)
984982

985-
# Recommended: SimpleLevels already provides the correct format
986-
# Just pass the parameter directly to check_levels
983+
# Always use check_levels for threshold checking:
987984
yield from check_levels(
988985
value,
989-
levels_upper=params.get('levels'), # Already in ("fixed", (warn, crit)) format
986+
levels_upper=params.get('levels'), # SimpleLevels format: ("fixed", (warn, crit)) or None
990987
metric_name="my_metric",
991988
label="My metric",
992-
render_func=lambda v: f"{v:.1f}", # Custom formatting
989+
render_func=lambda v: f"{v:.1f}",
993990
)
994991
```
995992

996-
##### Benefits of check_levels
993+
**Why use check_levels:**
994+
- Automatic state determination (OK/WARN/CRIT)
995+
- Consistent output formatting
996+
- Automatic metric generation with proper boundaries
997+
- Handles None levels gracefully
998+
- Supports predictive levels and other advanced features
997999

998-
- **Automatic state determination**: Handles WARN/CRIT state logic
999-
- **Consistent formatting**: Proper threshold display in output
1000-
- **Metric generation**: Creates performance data automatically
1001-
- **SimpleLevels compatibility**: Works with CheckMK 2.3 rulesets
1002-
- **Error handling**: Robust handling of invalid parameters
1003-
- **Extensibility**: Supports predictive levels and other advanced features
10041000

10051001
##### Common Patterns
10061002

@@ -1038,7 +1034,7 @@ def check_comprehensive_metrics(item: str, params: Mapping[str, Any], section: D
10381034

10391035
## Agent Bakery Integration
10401036

1041-
The Agent Bakery allows centralized configuration and deployment of agent plugins across multiple hosts using the `cmk.base.pyugins.bakery.bakery_api.v1` API.
1037+
The Agent Bakery allows centralized configuration and deployment of agent plugins across multiple hosts using the `cmk.base.plugins.bakery.bakery_api.v1` API.
10421038

10431039
**Important**: CheckMK 2.3 bakery integration requires **two separate files**:
10441040
1. **Bakery Plugin** - Technical logic for plugin generation and deployment
@@ -1049,7 +1045,7 @@ The Agent Bakery allows centralized configuration and deployment of agent plugin
10491045
# File: ~/local/lib/check_mk/base/cee/plugins/bakery/my_service.py
10501046

10511047
import json
1052-
from cmk.base.pyugins.bakery.bakery_api.v1 import (
1048+
from cmk.base.plugins.bakery.bakery_api.v1 import (
10531049
register,
10541050
Plugin,
10551051
PluginConfig,
@@ -1178,7 +1174,7 @@ rule_spec_my_service_bakery = AgentConfig(
11781174
```python
11791175
# File: ~/local/lib/check_mk/base/cee/plugins/bakery/my_service_advanced.py
11801176

1181-
from cmk.base.pyugins.bakery.bakery_api.v1 import (
1177+
from cmk.base.plugins.bakery.bakery_api.v1 import (
11821178
register,
11831179
Plugin,
11841180
PluginConfig,
@@ -1485,7 +1481,7 @@ def _advanced_form_spec():
14851481
required=True,
14861482
),
14871483
"read_timeout": DictElement(
1488-
parameter_form=TimeSpan(
1484+
parameter_form=TimeSpan(
14891485
title=Title("Read timeout"),
14901486
prefill=DefaultValue(60.0),
14911487
),
@@ -1980,7 +1976,7 @@ python3 -m py_compile ~/local/lib/python3/cmk_addons/plugins/agent_based/my_serv
19801976
import logging
19811977

19821978
# Set up logging
1983-
logger = logging.getLogger("cmk.base.pyugins.agent_based.my_service")
1979+
logger = logging.getLogger("cmk.base.plugins.agent_based.my_service")
19841980

19851981
def parse_my_service(string_table: list[list[str]]) -> Dict[str, Any]:
19861982
"""Parse with debugging"""
@@ -2021,7 +2017,7 @@ from typing import List
20212017
from cmk.agent_based.v2 import Result, State, Service
20222018

20232019
# Import your plugin functions
2024-
from cmk_addons.pyugins.agent_based.my_service import (
2020+
from cmk_addons.plugins.agent_based.my_service import (
20252021
parse_my_service,
20262022
discover_my_service,
20272023
check_my_service,
@@ -2529,7 +2525,7 @@ rule_spec_temperature_monitor = CheckParameters(
25292525
topic=Topic.ENVIRONMENT,
25302526
name="temperature_monitor",
25312527
parameter_form=_temperature_monitor_form_spec,
2532-
condition=HostAndServiceCondition(service_name="Temperature"),
2528+
condition=HostAndItemCondition(item_title=Title("Sensor name")),
25332529
)
25342530
```
25352531

@@ -2597,7 +2593,7 @@ perfometer_temperature = Perfometer(
25972593
```python
25982594
# File: ~/local/lib/check_mk/base/cee/plugins/bakery/temperature_monitor.py
25992595

2600-
from cmk.base.pyugins.bakery.bakery_api.v1 import (
2596+
from cmk.base.plugins.bakery.bakery_api.v1 import (
26012597
register,
26022598
Plugin,
26032599
OS,
@@ -2695,6 +2691,38 @@ For additional resources:
26952691
- Community forums for support and discussion
26962692
- Local API documentation in `check_mk/plugin-api/html/`
26972693

2694+
## Common Pitfalls and Solutions
2695+
2696+
### Import and Path Errors
2697+
2698+
**Problem**: `ModuleNotFoundError` for bakery imports
2699+
- **Cause**: Typo in import path (`cmk.base.pyugins` instead of `cmk.base.plugins`)
2700+
- **Solution**: Use `from cmk.base.plugins.bakery.bakery_api.v1 import ...`
2701+
2702+
**Problem**: Bakery plugin not found
2703+
- **Cause**: Wrong directory path for bakery plugins
2704+
- **Solution**: Place in `~/local/lib/check_mk/base/cee/plugins/bakery/`
2705+
2706+
### SimpleLevels and check_levels Errors
2707+
2708+
**Problem**: `TypeError: '>=' not supported between instances of 'float' and 'tuple'`
2709+
- **Cause**: Wrapping SimpleLevels parameters that are already in correct format
2710+
- **Solution**: Pass SimpleLevels parameters directly to check_levels without modification
2711+
2712+
### Ruleset Configuration Errors
2713+
2714+
**Problem**: `TypeError: HostAndServiceCondition.__init__() got an unexpected keyword argument`
2715+
- **Cause**: Using wrong condition type for check plugin
2716+
- **Solution**:
2717+
- Use `HostAndItemCondition` for checks with items (multiple services per host)
2718+
- Use `HostAndServiceCondition` for checks without items (single service per host)
2719+
2720+
### Check Plugin Discovery Issues
2721+
2722+
**Problem**: Plugin not discovered by CheckMK
2723+
- **Cause**: Missing or incorrect entry point prefixes
2724+
- **Solution**: Name variables correctly: `agent_section_*`, `check_plugin_*`
2725+
26982726
## CheckMK Color Class Constants
26992727

27002728
The `cmk.graphing.v1.Color` class provides predefined color constants for use in graphing definitions. These constants ensure consistent color usage across CheckMK visualizations.

0 commit comments

Comments
 (0)