Convert DropdownChoice() to Dictionary()?

How do I migrate from a bakery ruleset that used DropdownChoice() to the rulesets.v1 API with Dictionary()?

The documentation has this example for a simple bakery ruleset:

def _parameter_form_bakery():
    return Dictionary(
        elements = {}
    )

The dictionary obviously does not need any elements because we just want to bake the plugin into the agent package.

In the past I used something like this:

def _valuespec_agent_config_apcaccess():
    return DropdownChoice(
        title = _("APC UPS via apcaccess (Linux, Windows)"),
        help = _("This will deploy the agent plugin <tt>apcaccess</tt> to check various APC UPS stats."),
        choices = [
            ( True, _("Deploy plugin for APC UPS") ),
            ( None, _("Do not deploy plugin for APC UPS") ),
        ]
    )

because this made it possible to not deploy the agent plugin to specific hosts. Now only “True” is stored as rule value in rules.mk. And a migrate function does not get this value, the GUI only tells me: “The value of this rule is not valid. True”.

Should I replace the DropdownChoice with SingleChoice?

1 Like

I have now the following code for the ruleset:

def _migrate_from_bool_to_dict(param):
    if isinstance(param, bool) and param:
        return {"deploy": "yes"}
    if not param:
        return {"deploy": "no"}
    return param

def _valuespec_agent_config_apcaccess():
    return Dictionary(
        elements={
            "deploy": DictElement(
                required=True,
                parameter_form=SingleChoice(
                    prefill=DefaultValue("yes"),
                    elements=[
                        SingleChoiceElement(
                            name="yes",
                            title=Title("Deploy plugin for APC UPS"),
                        ),
                        SingleChoiceElement(
                            name="no",
                            title=Title("Do not deploy plugin for APC UPS"),
                        ),
                    ]
                ),
            )
        },
        migrate=_migrate_from_bool_to_dict,
    )

rule_spec_sslcertificates_bakery = AgentConfig(
    name="apcaccess",
    title=Title("APC UPS via apcaccess (Linux, Windows)"),
    help_text=Help("This will deploy the agent plugin <tt>apcaccess</tt> to check various APC UPS stats."),
    topic=Topic.APPLICATIONS,
    parameter_form=_valuespec_agent_config_apcaccess,
)

and the corresponding bakery plugin:

def get_apcaccess_files(conf: Dict[str, Any]) -> FileGenerator:
    if isinstance(conf, bool) and conf:
        conf = {"deploy": "yes"}
    if conf.get("deploy") == "yes":
        yield Plugin(base_os=OS.LINUX,
                    source=Path("apcaccess"))
        yield Plugin(base_os=OS.WINDOWS,
                    source=Path("apcaccess.bat"))

register.bakery_plugin(
    name="apcaccess",
    files_function=get_apcaccess_files,
)

But the migrate function does not even get called for the old rule value. The already configured rule from the old version of the extension looks like this:

{'condition': {'host_name': ['apcaccess']},
 'id': '3ea91f24-2243-4cdc-bf6b-6069be6f5fe6',
 'options': {'disabled': False},
 'value': True},
1 Like

For all those who want a slightly simpler variant (with bools), you can also do the following (also used by Checkmk itself):

def _valuespec_agent_config_apcaccess() -> Dictionary:
    return Dictionary(
        elements={
            "deploy": DictElement(
                required=True,
                parameter_form=BooleanChoice(
                    label=Label("Deploy plugin for APC UPS"),
                    prefill=DefaultValue(True),
                ),
            ),
        }
    )
1 Like

Yes, that looks simpler.
But it does not solve the issue of the conversion of the previous rule value True to {"deploy": True}.

1 Like

The migration part looks like this (inspired by the Ceph plugin):

def migrate_bakery_rule(value: object) -> Mapping[str, object]:
    match value:
        case bool(deploy):
            return {"deploy": deploy}
        case None:
            return {"deploy": False}
        case dict():
            return {"deploy": True, **value}
    raise ValueError(value)
1 Like

My issue is that the migrate function does not get called when the rule value is just True.

In the list of agent packages in the bakery this is shown as:

Failed to render value:
True
Traceback (most recent call last):
  File "/omd/sites/dev23/lib/python3/cmk/gui/cee/agent_bakery/_misc.py", line 293, in _describe_agent_configuration
    totext = rulespec.valuespec.value_to_html(value)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/omd/sites/dev23/lib/python3/cmk/gui/valuespec.py", line 6725, in value_to_html
    return self._valuespec.value_to_html(self.to_valuespec(value))
                                         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/omd/sites/dev23/lib/python3/cmk/gui/utils/rule_specs/legacy_converter.py", line 373, in _remove_agent_config_match_type_key
    raise TypeError(value)
TypeError: True
1 Like

Seen exactly this in the Developer Hour (Developer Hour: New DevAPIs)

https://www.youtube.com/live/bb4CnEpcKAM

Source code:

2 Likes

And I have basically the same code:

I added the prints, the output ends up in $OMD_ROOT/var/log/apache/error.log.

There is no call to the migrate function with the param value of True.

I do not know how to convert the old rules.

1 Like

Hello Robert,

elements (she has Dict->DictElement->SingleChoice->SingleChoiceElement) and migrate function (she has True, None, dict, ValueError as conditions and returns {str: str} and not {str: bool}) differ.

I think you can exactly copy @rebekka’s code from the PR because it’s the same use case (deploy/do_not_deploy). And you have to follow the structure with the cee-subdirectory and the __init__.py like she explained in the forum and the video.

1 Like

Yes, but this is not the issue.
The issue is that the migrate function does not get called with the old rule value.

1 Like

Migrate functions are only executed at “cmk-update-config” not at runtime if i remember it correctly.

1 Like

You remember incorrectly. I see the execution of the migrate function in the Apache error.log when adding a print() statement. It does not get called for the rule with "value": True.

cmk-update-config -v produces this error:

WARNING: Invalid rule configuration detected
Ruleset: agent_config:apcaccess
Title: APC UPS via apcaccess (Linux, Windows)
Folder: main
Rule nr: 1
Exception: True

You can abort the update process (A) or continue (c) the update. Abort update? [A/c]

@moritz , do you have any clues?

I don’t see anything wrong with your code. I’ll try to reproduce it. Meanwhile: What happens if you open and save the rule in the GUI? Will it be migrated then?

Works for me :-/. What I did:

  • downloaded your file from github
  • set up a site (daily build of 2.4) and put your file into local/lib/python3/cmk{,_addons}/plugins/collection/rulesets/apcaccess.py (I tried both)
  • added a rule (via GUI), faked a "value": True in the rule (via editing rules.mk)
  • stopped the site, ran cmk-update-config => value is migrated
  • faked the old value again, started the site, opened/saved the rule => value is imgrated
    Not sure what’s going on :frowning:

My dev site is on 2.3.0p29 because I still need to migrate extensions away from Check API v1.

I have now updated to 2.3.0p31 and omd update outputs this:

-|  11/32 Rulesets...
-| ERROR: Failed to transform rule: (Ruleset: agent_config:apcaccess, Folder: , Rule: 0, Value: True: True
-| Before: {'deploy': True}
-| After: {'deploy': True}
-| Before: {'deploy': False}
-| After: {'deploy': False}
-|  + "Rulesets" failed
-| Traceback (most recent call last):
-|   File "/omd/sites/dev23/lib/python3/cmk/update_config/main.py", line 270, in update_config
-|     action(logger, update_state.setdefault(action.name))
-|   File "/omd/sites/dev23/lib/python3/cmk/update_config/plugins/actions/rulesets.py", line 84, in __call__
-|     _validate_rule_values(logger, all_rulesets)
-|   File "/omd/sites/dev23/lib/python3/cmk/update_config/plugins/actions/rulesets.py", line 370, in _validate_rule_values
-|     ruleset.rulespec.valuespec.validate_value(
-|   File "/omd/sites/dev23/lib/python3/cmk/gui/valuespec.py", line 362, in validate_value
-|     self._validate_value(value, varprefix)
-|   File "/omd/sites/dev23/lib/python3/cmk/gui/valuespec.py", line 6743, in _validate_value
-|     self._valuespec.validate_value(self.to_valuespec(value), varprefix)
-|                                    ^^^^^^^^^^^^^^^^^^^^^^^^
-|   File "/omd/sites/dev23/lib/python3/cmk/gui/utils/rule_specs/legacy_converter.py", line 373, in _remove_agent_config_match_type_key
-|     raise TypeError(value)
-| TypeError: True

I clearly shows that the migrate function only gets called (twice) for the two rules with a valid value and outputs an error for the rule with 'value': True.

Hello Robert,

Perhaps nothing related to the problem, but you define “rule_spec_sslcertificates_bakery” in “apcaccess”. If you have the MKP sslcertificates installed, a side effect can be the reason you don’t see what you expect.

Github link:
rule_spec_sslcertificates_bakery = AgentConfig(

1 Like

A classical copy&paste mishap. But changing the name of this object does not resolve the issue.

This error specifically occurs when using migrations together with AgentConfigs, I probably should have mentioned that during the developer hour. Sadly the fix is not yet released, but is coming soon.
As a hot fix /omd/sites/dev23/lib/python3/cmk/gui/utils/rule_specs/legacy_converter.py could be replaced with this version until then: checkmk/cmk/gui/utils/rule_specs/legacy_converter.py at 2.3.0 ¡ Checkmk/checkmk ¡ GitHub

4 Likes

In contrast to what the werk says (2.3.0p32) the fix is not included in this release. :frowning:

Yes, the 2.3.0p32 was a security release that only contained security fixes. Unfortunately, a number of non-security related werks were mislabeled as also being included.
The incorrect werks are being changed to fix that.

1 Like