Checkmk
to checkmk.com

1. Introduction

CEE In the commercial editions you can use the Bakery API to write your own, so-called Bakery plug-ins, which include functions in the agent packages from the Agent Bakery. In most cases, these functions are agent plug-ins, i.e. additional scripts to be executed by the Checkmk agent, and the plug-ins' configuration files. However, they can also affect the package manager functions if they can be mapped by including files, running package scriptlets (for RPM, DEB and Solaris PKG package formats), or if they define specific configuration entries for the Windows agent (in YAML). All of these 'artifacts' can be described in the Bakery API using a uniform syntax.

For example, one real-world application scenario is as follows: You read the introduction to developing extensions for Checkmk and, inspired by it, wrote your own agent-based check plug-in with its associated agent plug-in. You then combined these two into a single Checkmk extension package (MKP). In the Checkmk exchange, we have stored the MKP Hello world! as a simple template for this example.

Now you want to make the agent plug-in configurable (e.g., enable it to be run only by certain users or on certain hosts) and additionally perform actions when installing or uninstalling the agent package. To do this, you can use the Bakery API — as a packaging and distribution aid, as we will show in this article with an example scenario. This creates two new files, which can then be packaged together with the existing plug-in files to create a new MKP. You can also find a thoroughly commented example of this procedure in the Checkmk Exchange: the Hello bakery! MKP, which is closely based on the example scenario presented in this article.

Note: The Bakery API does not provide functions for configuring the Bakery plug-in, i.e. for creating the associated rule set, nor for the contents of the files provided with the plug-in, e.g. the agent plug-ins.

Tip

Even though the Agent Bakery is only included in the commercial editions, the Bakery API exists in all editions since Checkmk 2.3.0. This allows Checkmk Raw users to create extension packages that can be installed on all editions. If packages created with the Bakery API are installed on a Checkmk Raw, the additional functionality is simply ignored.

2. The API documentation

2.1. Versioning

Bakery API software and documentation come from the same source, so the API documentation always matches the software and describes exactly what the API can do, and it is therefore not necessary to describe the reference part of the available functions, classes, parameters, etc. in the Checkmk User guide. Instead, you can find the API documentation outside this User guide, directly in its Checkmk site.

The API with its documentation is versioned using two-level numbering conforming to the Semantic Versioning 2.x standard in the format X.Y, where X stands for a major version and Y for a minor version. A new minor version contains new, backward compatible features. A new major version, on the other hand, may contain changes that make the API incompatible with the previous major version.

Version 1 is the current version of the Bakery API described in this article. Each plug-in explicitly declares the API version it is based on at the access to the API.

The API follows a different versioning scheme to the Checkmk software. Nevertheless, mapping the versions of API documentation and Checkmk software is very simple, as you will learn in the next chapter.

2.2. Access to the API documentation

The Bakery API documentation is available in HTML format for viewing in a web browser and can be opened from the Checkmk GUI: from the navigation bar in the Help > Developer resources > Plug-in API references menu:

Help menu in the navigation bar.

The plug-in API documentation is displayed in a new browser window (or browser tab):

Example page of Bakery API documentation.

This window displays API documentation relevant to the development of Checkmk plug-ins, i.e. here you will find the documentation for the Check API in addition to the Bakery API documentation. The API documentation is generated and displayed with Sphinx.

You can find the Bakery API documentation in the versions supported by the Checkmk version installed in your site.

3. Using the API

3.1. A sample scenario

We will demonstrate the use of the API with the following sample scenario:

  • A plug-in with the name hello_bakery is provided for the Checkmk agent.

  • The agent plug-in exists in three variants — for Linux, Solaris and Windows — and is to be included in the agent packages for these three operating systems as well.The corresponding files are also available and are called hello_bakery (for Linux), hello_bakery.solaris.ksh (for Solaris) and hello_bakery.cmd (for Windows).
    The Python, shell and CMD scripts are only examples. An agent plug-in can be any file executable on the target system.
    We are not interested in the actual content of the files in this context. The function of agent plug-ins is not the subject of the Bakery API. You can learn more about this in the introduction to writing your own agent-based check plug-ins.

  • It should be configurable whether the plug-in’s output should be cached, i.e. in this case the plug-in will only be executed again by the agent once the configured time has elapsed.

  • The plug-in is to be configured in the Setup menu via Agent Bakery settings with the variables user and content. The Linux plug-in reads the configuration from the hello_bakery.json configuration file, and the Solaris plug-in reads it from the hello_bakery.cfg file. The Windows plug-in reads the hello_bakery.user and hello_bakery.content entries from the YAML configuration file in the Windows agent.
    In each case, access to these resources must be implemented in the agent plug-in and is not handled by the Bakery API.

  • For Linux and Solaris there is an additional program some_binary, which should be delivered e.g. a small shell script, to also be able to start the plug-in by a command independently of the Checkmk agent.

  • Under Linux and Solaris, after installing the agent, it should be written to the syslog via a package manager routine that hello_bakery has been installed. After uninstalling the agent, analogously, it should be written to syslog that hello_bakery has been uninstalled.
    Common under Linux are postinst and prerm scripts: in the postinst script you create for example a cache and start a daemon, in the prerm script you can then stop the daemon again and clear the cache. For more information on how to use maintainer scripts, see the Debian documentation.

3.2. Creating a plug-in file

The hello_bakery.py plug-in file is created in the local part of the site directory structure at local/lib/check_mk/base/cee/plugins/bakery/.

A Bakery plug-in is created in the form of a file that is imported as a Python 3 module. Therefore, following Checkmk convention, plug-in files also start with the following lines:

hello_bakery.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

Since this is a module, all required names must be imported at the beginning.

3.3. Accessing the API

All objects in the Bakery API are available under cmk.base.cee.plugins.bakery.bakery_api.vX, where X denotes the API version number, in the example 1. Since the plug-in file itself is located in the cmk.base.cee.plugins.bakery namespace, a relative import from .bakery_api.v1 will also work:

hello_bakery.py
from .bakery_api.v1 import (
   OS,
   DebStep,
   RpmStep,
   SolStep,
   Plugin,
   PluginConfig,
   SystemBinary,
   Scriptlet,
   WindowsConfigEntry,
   register,
   FileGenerator,
   ScriptletGenerator,
   WindowsConfigGenerator,
   quote_shell_string,
)

In the above example, only the names that are needed for the example scenario are imported.

3.4. The objects available in the API

The names available in the Bakery API are described in detail in the API documentation. In this chapter, the objects are nevertheless briefly introduced, as this is helpful for being able to understand the implementation of the example scenario.

Identifier / Enumerations

For the specification of the individual plug-in artifacts, enumerations (Enum) are available, which can be used to specify various properties, usually in the form of an argument:

  • OS - The operating system in the context of the Bakery API.

  • DebStep - A transaction step for a DEB 'maintainer script'

  • RpmStep - A transaction step for an RPM 'scriptlet'.

  • SolStep - A transaction step for a Solaris PKG 'installation script'.

Artifacts

The files and file contents that are the actual components of a plug-in are referred to as artifacts. These are described using appropriate classes, which can be divided into the following categories:

  • Files (Plugin, SystemBinary, PluginConfig, SystemConfig) - Each file to be provided to the Checkmk agent is described with an object. The file type is described by the class. A separate object must be defined for each operating system on which the file is to be deployed.

  • Scriptlets (Scriptlet) - A DEB 'maintainer script', RPM 'scriptlet' or Solaris PKG 'installation script' to be executed when installing, uninstalling or updating the agent package at the specified transaction step (e.g. preinstall, postremove).

  • Windows configuration entries (WindowsConfigEntry, WindowsConfigItems, WindowsGlobalConfigEntry, WindowsSystemConfigEntry) - Entries in the YAML configuration file for the Windows agent are also described using appropriate classes.

These artifacts are each described in callback functions corresponding to their category. The individual functions are passed to the register function with the arguments files_function, scriptlets_function, windows_config_function. These are generator functions that return the individual specified artifacts. The evaluation is done by the Agent Bakery.

The functions receive various parameters as arguments that can be evaluated in order to construct and determine the returned artifacts. The parameters are on the one hand the GUI configuration of the respective agent that is to be baked (conf), and on the other hand the hash of the current agent configuration and plug-in files (aghash).

The register function

The registration is performed with the register function, which is called when importing the Bakery plug-in as a module.

The function receives the individual components of the Bakery plug-in as arguments: the plug-in’s name (name) and its functions (files_function, scriptlets_function, windows_config_function), each of which returns a category of artifact.

Type annotations

Names for type annotations (FileGenerator, ScriptletGenerator, WindowsConfigGenerator, WindowsConfigContent) can optionally be used to identify the type of the function specified, e.g. like this:

def get_files(conf: dict) -> FileGenerator:
  	yield Plugin(...)
  	yield PluginConfig(...)

def get_scriptlets(conf: dict) -> ScriptletGenerator:
  	yield Scriptlet(...)

def get_windows_config(conf: dict) -> WindowsConfigGenerator:
  	content: WindowsConfigContent = conf["some_entry"]
   yield WindowsGlobalConfigEntry(name="some_name",content=content)

Helper functions

The following helper functions can be used:

  • quote_shell_string - This function can be used to convert a string expression so that it is correctly recognized as an expression by the shell in the resulting file — without having to manually mask the quotes in the Python code.

  • password_store - This module allows access to passwords stored in the Checkmk password store.

3.5. Registration

Registering the plug-in with Checkmk with a plug-in name and its functions is accomplished with the register.bakery_plugin function:

hello_bakery.py
register.bakery_plugin(
   name="hello_bakery",
   files_function=get_hello_bakery_plugin_files,
   scriptlets_function=get_hello_bakery_scriptlets,
   windows_config_function=get_hello_bakery_windows_config,
)

The get_hello_bakery_windows_config, get_hello_bakery_scriptlets and get_hello_bakery_plugin_files functions specified here are explained in more detail in the following chapters.

3.6. Configuration for the Windows agent

In our example, the interval for execution must be be defined, and the configuration of the plug-in should be able to be done via two variables:

hello_bakery.py
class HelloBakeryConfig(TypedDict, total=False):
   interval: int
   user: str
   content: str

def get_hello_bakery_windows_config(conf: HelloBakeryConfig) -> WindowsConfigGenerator:
   yield WindowsConfigEntry(path=["hello_bakery", "user"], content=conf["user"])
   yield WindowsConfigEntry(path=["hello_bakery", "content"], content=conf["content"])
)

In the get_hello_bakery_windows_config function, we use the conf argument to access the configuration set via the rule set in the Setup GUI: The time interval for re-execution on cached output (interval) and the two variables that can be used to configure the plug-in (user, content). Here we assume that the rule set configuration is supplied as a dict. Using the TypedDict of the class HelloBakeryConfig we can set up a standardized access to it.

Then WindowsConfigEntry is used to specify the entries in the YAML-Windows agent configuration file from which the values for user and content are read.

3.7. Installation script for Linux

On Linux and Solaris, syslog messages should be written when installing and uninstalling the agent. Here we show only an implementation of the Debian Linux distribution:

hello_bakery.py
def get_hello_bakery_scriptlets(conf: HelloBakeryConfig) -> ScriptletGenerator:
   installed_lines = ['logger -p local3.info "Installed hello_bakery"']
   uninstalled_lines = ['logger -p local3.info "Uninstalled hello_bakery"']
   # Adjust the priority -p according to your needs.

   yield Scriptlet(step=DebStep.POSTINST, lines=installed_lines)
   yield Scriptlet(step=DebStep.POSTRM, lines=uninstalled_lines)

First the commands for the syslog messages are defined and then the installation scripts for Debian (DebStep) which should be executed after the installation (POSTINST) and after the uninstallation (POSTRM). In the comments below you will also find the corresponding lines for distributions that use RPM and for Solaris.

Note: Following the command lines that you have included, the installation scripts are loaded with additional commands by Checkmk. Therefore, to ensure that all of the commands in the scripts are executed, do not end your command set with an exit 0.

3.8. The agent plug-in for Linux

The configuration for the Linux agent plug-in looks like this:

hello_bakery.py
def get_hello_bakery_plugin_files(conf: HelloBakeryConfig) -> FileGenerator:
   interval = conf.get('interval')

   yield Plugin(
      base_os=OS.LINUX,
      source=Path('hello_bakery'),
      target=Path('hello_bakery'),
      interval=interval,
   )

   yield PluginConfig(base_os=OS.LINUX,
                     lines=_get_linux_cfg_lines(conf['user'], conf['content']),
                     target=Path('hello_bakery.json'),
                     include_header=False)

   for base_os in [OS.LINUX]:
      yield SystemBinary(
            base_os=base_os,
            source=Path('some_binary'),
      )

def _get_linux_cfg_lines(user: str, content: str) -> List[str]:
   config = json.dumps({'user': user, 'content': content})
   return config.split('\n')

In the get_hello_bakery_plugin_files function, first the Python file hello_bakery is defined as a plugin, i.e. as an executable file to be run by the Checkmk agent as an agent plug-in. Then PluginConfig is used to specify the hello_bakery.json configuration file to be generated for the Linux agent plug-in with the user and content entries.

With the second function _get_linux_cfg_lines these lines are written in JSON format. Here, the Python dictionary conf contains the values set with the rule set of the Setup GUI, which are then packed into a JSON file via a small detour.

Finally, the additional shell script some_binary to be delivered is to be placed as SystemBinary on the target system in the directory for user programs (by default /usr/bin).

3.9. The plug-in file for our example scenario

Putting the all of the parts presented so far together — and completing them — a plug-in for our example scenario might look like this when finished:

hello_bakery.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
from pathlib import Path
from typing import Iterable, TypedDict, List

from .bakery_api.v1 import (
   OS,
   DebStep,
   RpmStep,
   SolStep,
   Plugin,
   PluginConfig,
   SystemBinary,
   Scriptlet,
   WindowsConfigEntry,
   register,
   FileGenerator,
   ScriptletGenerator,
   WindowsConfigGenerator,
   quote_shell_string,
)

class HelloBakeryConfig(TypedDict, total=False):
   interval: int
   user: str
   content: str

def get_hello_bakery_plugin_files(conf: HelloBakeryConfig) -> FileGenerator:
   interval = conf.get('interval')

   yield Plugin(
      base_os=OS.LINUX,
      source=Path('hello_bakery'),
      target=Path('hello_bakery'),
      interval=interval,
   )
   yield Plugin(
      base_os=OS.SOLARIS,
      source=Path('hello_bakery.solaris.ksh'),
      target=Path('hello_bakery'),
      interval=interval,
   )
   yield Plugin(
      base_os=OS.WINDOWS,
      source=Path('hello_bakery.cmd'),
      target=Path('hello_bakery.bat'),
      interval=interval,
   )

   yield PluginConfig(base_os=OS.LINUX,
                     lines=_get_linux_cfg_lines(conf['user'], conf['content']),
                     target=Path('hello_bakery.json'),
                     include_header=False)
   yield PluginConfig(base_os=OS.SOLARIS,
                     lines=_get_solaris_cfg_lines(conf['user'], conf['content']),
                     target=Path('hello_bakery.cfg'),
                     include_header=True)

   for base_os in [OS.LINUX, OS.SOLARIS]:
      yield SystemBinary(
            base_os=base_os,
            source=Path('some_binary'),
      )

def _get_linux_cfg_lines(user: str, content: str) -> List[str]:
   config = json.dumps({'user': user, 'content': content})
   return config.split('\n')

def _get_solaris_cfg_lines(user: str, content: str) -> List[str]:
   # To be loaded with 'source' in Solaris shell script
   return [
      f'USER={quote_shell_string(user)}',
      f'CONTENT={quote_shell_string(user)}',
   ]

def get_hello_bakery_scriptlets(conf: HelloBakeryConfig) -> ScriptletGenerator:
   installed_lines = ['logger -p local3.info "Installed hello_bakery"']
   uninstalled_lines = ['logger -p local3.info "Uninstalled hello_bakery"']

   yield Scriptlet(step=DebStep.POSTINST, lines=installed_lines)
   yield Scriptlet(step=DebStep.POSTRM, lines=uninstalled_lines)
   yield Scriptlet(step=RpmStep.POST, lines=installed_lines)
   yield Scriptlet(step=RpmStep.POSTUN, lines=uninstalled_lines)
   yield Scriptlet(step=SolStep.POSTINSTALL, lines=installed_lines)
   yield Scriptlet(step=SolStep.POSTREMOVE, lines=uninstalled_lines)

def get_hello_bakery_windows_config(conf: HelloBakeryConfig) -> WindowsConfigGenerator:
   yield WindowsConfigEntry(path=["hello_bakery", "user"], content=conf["user"])
   yield WindowsConfigEntry(path=["hello_bakery", "content"], content=conf["content"])

register.bakery_plugin(
   name="hello_bakery",
   files_function=get_hello_bakery_plugin_files,
   scriptlets_function=get_hello_bakery_scriptlets,
   windows_config_function=get_hello_bakery_windows_config,
)

3.10. Creating a rule set

For a Bakery plug-in there must be a set of rules for the Setup, with which the plug-in can be configured via the GUI. In the simplest case this is the activation of the plug-in. In the most general case the configuration within the functions files_function, scriptlets_function and windows_config_function can be evaluated arbitrarily and influence the returned artifacts, e.g. to write the user name into the configuration. To the functions just mentioned, the configuration of the plug-in is available through the conf argument.

The required rule set must be specified as a plug-in file and also stored. The file name must match the plug-in name and end with the .py suffix.

The creation of a rule set is not part of the Bakery API. You can find an introduction to this topic in the writing your own agent-based check plug-ins article. However, since the rule set is closely related to the Bakery plug-in, we will show a possible implementation accompanying this example scenario:

hellobakery_bakery.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from cmk.gui.i18n import _
from cmk.gui.plugins.wato import (
   HostRulespec,
   rulespec_registry,
)
from cmk.gui.cee.plugins.wato.agent_bakery.rulespecs.utils import RulespecGroupMonitoringAgentsAgentPlugins
from cmk.gui.valuespec import (
   Age,
   Dictionary,
   TextAscii,
)

def _valuespec_hello_bakery():
   return Dictionary(
      title=_("Hello bakery! (Linux, Solaris, Windows)"),
      help=_("This will deploy my example plugin."),
      elements=[
         ("user", TextAscii(
            title=_("User for example plugin"),
            allow_empty=False,
         )),
         ("content", TextAscii(
            title=_("The actual content"),
            allow_empty=False,
         )),
         ("interval",
          Age(
            title=_("Run asynchronously"),
            label=_("Interval for collecting data"),
            default_value=300, # default: 5 minutes
          )),
      ],
      optional_keys=["interval"],
   )

rulespec_registry.register(
   HostRulespec(
      group=RulespecGroupMonitoringAgentsAgentPlugins,
      name="agent_config:hello_bakery",
      valuespec=_valuespec_hello_bakery,
   ))

Important: The Agent Bakery ignores a definition of match_type in the linked rule set and always sets match_type to 'first', which means that the first matching rule will be executed.

The GUI resulting from this rule set is shown in the following image:

GUI of the rule set used to configure the plug-in.

The configuration finds its way to the Bakery plug-in via the name under which the rule set is registered and the prefix agent_config:. In this case, the rule set must be registered under the same name as the Bakery plug-in, but with the additional prefix agent_config:.

3.11. Making the files available

For a Bakery plug-in to work properly, all of the files involved must be placed or written in the correct location in the site directory’s local structure.

These are on the one hand the plug-in file itself and on the other hand the objects returned by the files_function. These objects either describe configuration files that are created directly by the Bakery plug-in, or they refer to files that must be stored correctly so that they can be found when packaging the agent packages.

Objects of the Plugin and SystemBinary classes denote existing files that must be stored. The files described as PluginConfig and SystemConfig are yet to be generated based on the lines argument, so no files need to be stored here.

Finally, the set of files also includes the rule set file for the plug-in.

In the next and last chapter you will find the compilation of all directories.

4. Files and directories

Files for deploying a Bakery plug-in must be placed in the following directories. As always, all specifications here are relative to the site’s directory (e.g. /omd/sites/mysite).

File path Description

local/lib/check_mk/base/cee/plugins/bakery/

Directory for the Bakery plug-in (in our example hello_bakery.py).

local/share/check_mk/agents/plugins/

Directory for storing the Unix-like agent plug-ins.

local/share/check_mk/agents/windows/plugins

Directory for storing the Windows agent plug-ins.

local/share/check_mk/agents/

Directory for included programs or shell scripts for Unix-like operating systems (some_binary in the example).

local/share/check_mk/agents/windows/

Directory for supplied programs or shell scripts for Windows.

local/share/check_mk/web/plugins/wato

Directory for the rule set files for configuring the agent plug-in (in the example hellobakery_bakery.py) and also the associated check plug-in (e.g. for setting thresholds). Therefore, choose meaningful names to be able to tell the files apart.

On this page