Module Helper guide

Introduction

Writing a module for Ansible is largely described in existing documentation. However, a good part of that is boilerplate code that needs to be repeated every single time. That is where ModuleHelper comes to assistance: a lot of that boilerplate code is done.

Quickstart

See the example from Ansible documentation written with ModuleHelper. But bear in mind that it does not showcase all of MH’s features:

from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper


class MyTest(ModuleHelper):
    module = dict(
        argument_spec=dict(
            name=dict(type='str', required=True),
            new=dict(type='bool', required=False, default=False),
        ),
        supports_check_mode=True,
    )
    use_old_vardict = False

    def __run__(self):
        self.vars.original_message = ''
        self.vars.message = ''
        if self.check_mode:
            return
        self.vars.original_message = self.vars.name
        self.vars.message = 'goodbye'
        self.changed = self.vars['new']
        if self.vars.name == "fail me":
            self.do_raise("You requested this to fail")


def main():
    MyTest.execute()


if __name__ == '__main__':
    main()

Module Helper

Introduction

ModuleHelper is a wrapper around the standard AnsibleModule, providing extra features and conveniences. The basic structure of a module using ModuleHelper is as shown in the Quickstart section above, but there are more elements that will take part in it.

from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper

class MyTest(ModuleHelper):
    output_params = ()
    change_params = ()
    diff_params = ()
    facts_name = None
    facts_params = ()
    use_old_vardict = True
    mute_vardict_deprecation = False
    module = dict(
        argument_spec=dict(...),
        # ...
    )

After importing the ModuleHelper class, you need to declare your own class extending it.

See also

There is a variation called StateModuleHelper, which builds on top of the features provided by MH. See StateModuleHelper below for more details.

The easiest way of specifying the module is to create the class variable module with a dictionary containing the exact arguments that would be passed as parameters to AnsibleModule. If you prefer to create the AnsibleModule object yourself, just assign it to the module class variable. MH also accepts a parameter module in its constructor, if that parameter is used used, then it will override the class variable. The parameter can either be dict or AnsibleModule as well.

Beyond the definition of the module, there are other variables that can be used to control aspects of MH’s behavior. These variables should be set at the very beginning of the class, and their semantics are explained through this document.

The main logic of MH happens in the ModuleHelper.run() method, which looks like:

@module_fails_on_exception
def run(self):
    self.__init_module__()
    self.__run__()
    self.__quit_module__()
    output = self.output
    if 'failed' not in output:
        output['failed'] = False
    self.module.exit_json(changed=self.has_changed(), **output)

The method ModuleHelper.__run__() must be implemented by the module and most modules will be able to perform their actions implementing only that MH method. However, in some cases, you might want to execute actions before or after the main tasks, in which cases you should implement ModuleHelper.__init_module__() and ModuleHelper.__quit_module__() respectively.

Note that the output comes from self.output, which is a @property method. By default, that property will collect all the variables that are marked for output and return them in a dictionary with their values. Moreover, the default self.output will also handle Ansible facts and diff mode. Also note the changed status comes from self.has_changed(), which is usually calculated from variables that are marked to track changes in their content.

See also

More details in sections Parameters, variables, and output and Handling changes below.

See also

See more about the decorator @module_fails_on_exception below.

Another way to write the example from the Quickstart would be:

def __init_module__(self):
    self.vars.original_message = ''
    self.vars.message = ''

def __run__(self):
    if self.check_mode:
        return
    self.vars.original_message = self.vars.name
    self.vars.message = 'goodbye'
    self.changed = self.vars['new']

def __quit_module__(self):
    if self.vars.name == "fail me":
        self.do_raise("You requested this to fail")

Notice that there are no calls to module.exit_json() nor module.fail_json(): if the module fails, raise an exception. You can use the convenience method self.do_raise() or raise the exception as usual in Python to do that. If no exception is raised, then the module succeeds.

See also

See more about exceptions in section Exceptions below.

Ansible modules must have a main() function and the usual test for '__main__'. When using MH that should look like:

def main():
    MyTest.execute()


if __name__ == '__main__':
    main()

The class method execute() is nothing more than a convenience shorcut for:

m = MyTest()
m.run()

Optionally, an AnsibleModule may be passed as parameter to execute().

Parameters, variables, and output

All the parameters automatically become variables in the self.vars attribute, which is of the VarDict type. By using self.vars, you get a central mechanism to access the parameters but also to expose variables as return values of the module. As described in VarDict Guide, variables in VarDict have metadata associated to them. One of the attributes in that metadata marks the variable for output, and MH makes use of that to generate the module’s return values.

Important

The VarDict feature described was introduced in community.general 7.1.0, but there was a first implementation of it embedded within ModuleHelper. That older implementation is now deprecated and will be removed in community.general 11.0.0. After community.general 7.1.0, MH modules generate a deprecation message about using the old VarDict. There are two ways to prevent that from happening:

  1. Set mute_vardict_deprecation = True and the deprecation will be silenced. If the module still uses the old VarDict, it will not be able to update to community.general 11.0.0 (Spring 2026) upon its release.

  2. Set use_old_vardict = False to make the MH module use the new VarDict immediatelly. The new VarDict and its use is documented and this is the recommended way to handle this.

class MyTest(ModuleHelper):
    use_old_vardict = False
    mute_vardict_deprecation = True
    ...

These two settings are mutually exclusive, but that is not enforced and the behavior when setting both is not specified.

Contrary to new variables created in VarDict, module parameters are not set for output by default. If you want to include some module parameters in the output, list them in the output_params class variable.

class MyTest(ModuleHelper):
    output_params = ('state', 'name')
    ...

Another neat feature provided by MH by using VarDict is the automatic tracking of changes when setting the metadata change=True. Again, to enable this feature for module parameters, you must list them in the change_params class variable.

class MyTest(ModuleHelper):
    # example from community.general.xfconf
    change_params = ('value', )
    ...

See also

See more about this in Handling changes below.

Similarly, if you want to use Ansible’s diff mode, you can set the metadata diff=True and diff_params for module parameters. With that, MH will automatically generate the diff output for variables that have changed.

class MyTest(ModuleHelper):
    diff_params = ('value', )

def __run__(self):
    # example from community.general.gio_mime
    self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True)

Moreover, if a module is set to return facts instead of return values, then again use the metadata fact=True and fact_params for module parameters. Additionally, you must specify facts_name, as in:

class VolumeFacts(ModuleHelper):
    facts_name = 'volume_facts'

    def __init_module__(self):
        self.vars.set("volume", 123, fact=True)

That generates an Ansible fact like:

- name: Obtain volume facts
  some.collection.volume_facts:
    # parameters

- name: Print volume facts
  debug:
    msg: Volume fact is {{ ansible_facts.volume_facts.volume }}

Important

If facts_name is not set, the module does not generate any facts.

Handling changes

In MH there are many ways to indicate change in the module execution. Here they are:

Tracking changes in variables

As explained above, you can enable change tracking in any number of variables in self.vars. By the end of the module execution, if any of those variables has a value different then the first value assigned to them, then that will be picked up by MH and signalled as changed at the module output. See the example below to learn how you can enabled change tracking in variables:

# using __init_module__() as example, it works the same in __run__() and __quit_module__()
def __init_module__(self):
    # example from community.general.ansible_galaxy_install
    self.vars.set("new_roles", {}, change=True)

    # example of "hidden" variable used only to track change in a value from community.general.gconftool2
    self.vars.set('_value', self.vars.previous_value, output=False, change=True)

    # enable change-tracking without assigning value
    self.vars.set_meta("new_roles", change=True)

    # if you must forcibly set an initial value to the variable
    self.vars.set_meta("new_roles", initial_value=[])
    ...

If the end value of any variable marked change is different from its initial value, then MH will return changed=True.

Indicating changes with changed

If you want to indicate change directly in the code, then use the self.changed property in MH. Beware that this is a @property method in MH, with both a getter and a setter. By default, that hidden field is set to False.

Effective change

The effective outcome for the module is determined in the self.has_changed() method, and it consists of the logical OR operation between self.changed and the change calculated from self.vars.

Exceptions

In MH, instead of calling module.fail_json() you can just raise an exception. The output variables are collected the same way they would be for a successful execution. However, you can set output variables specifically for that exception, if you so choose.

def __init_module__(self):
    if not complex_validation():
        self.do_raise("Validation failed!")

    # Or passing output variables
    awesomeness = calculate_awesomeness()
    if awesomeness > 1000:
        self.do_raise("Over awesome, I cannot handle it!", update_output={"awesomeness": awesomeness})

All exceptions derived from Exception are captured and translated into a fail_json() call. However, if you do want to call self.module.fail_json() yourself it will work, just keep in mind that there will be no automatic handling of output variables in that case.

StateModuleHelper

Many modules use a parameter state that effectively controls the exact action performed by the module, such as state=present or state=absent for installing or removing packages. By using StateModuleHelper you can make your code like the excerpt from the gconftool2 below:

from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper

class GConftool(StateModuleHelper):
    ...
    module = dict(
        ...
    )
    use_old_vardict = False

    def __init_module__(self):
        self.runner = gconftool2_runner(self.module, check_rc=True)
        ...

        self.vars.set('previous_value', self._get(), fact=True)
        self.vars.set('value_type', self.vars.value_type)
        self.vars.set('_value', self.vars.previous_value, output=False, change=True)
        self.vars.set_meta('value', initial_value=self.vars.previous_value)
        self.vars.set('playbook_value', self.vars.value, fact=True)

    ...

    def state_absent(self):
        with self.runner("state key", output_process=self._make_process(False)) as ctx:
            ctx.run()
            self.vars.set('run_info', ctx.run_info, verbosity=4)
        self.vars.set('new_value', None, fact=True)
        self.vars._value = None

    def state_present(self):
        with self.runner("direct config_source value_type state key value", output_process=self._make_process(True)) as ctx:
            ctx.run()
            self.vars.set('run_info', ctx.run_info, verbosity=4)
        self.vars.set('new_value', self._get(), fact=True)
        self.vars._value = self.vars.new_value

Note that the method __run__() is implemented in StateModuleHelper, all you need to implement are the methods state_<state_value>. In the example above, community.general.gconftool2 only has two states, present and absent, thus, state_present() and state_absent().

If the controlling parameter is not called state, like in community.general.jira module, just let SMH know about it:

class JIRA(StateModuleHelper):
    state_param = 'operation'

    def operation_create(self):
        ...

    def operation_search(self):
        ...

Lastly, if the module is called with state=somevalue and the method state_somevalue is not implemented, SMH will resort to call a method called __state_fallback__(). By default, this method will raise a ValueError indicating the method was not found. Naturally, you can override that method to write a default implementation, as in community.general.locale_gen:

def __state_fallback__(self):
    if self.vars.state_tracking == self.vars.state:
        return
    if self.vars.ubuntu_mode:
        self.apply_change_ubuntu(self.vars.state, self.vars.name)
    else:
        self.apply_change(self.vars.state, self.vars.name)

That module has only the states present and absent and the code for both is the one in the fallback method.

Note

The name of the fallback method does not change if you set a different value of state_param.

Other Conveniences

Delegations to AnsibleModule

The MH properties and methods below are delegated as-is to the underlying AnsibleModule instance in self.module:

  • check_mode

  • get_bin_path()

  • warn()

  • deprecate()

Additionally, MH will also delegate:

  • diff_mode to self.module._diff

  • verbosity to self.module._verbosity

Decorators

The following decorators should only be used within ModuleHelper class.

@cause_changes

This decorator will control whether the outcome of the method will cause the module to signal change in its output. If the method completes without raising an exception it is considered to have succeeded, otherwise, it will have failed.

The decorator has a parameter when that accepts three different values: success, failure, and always. There are also two legacy parameters, on_success and on_failure, that will be deprecated, so do not use them. The value of changed in the module output will be set to True:

  • when="success" and the method completes without raising an exception.

  • when="failure" and the method raises an exception.

  • when="always", regardless of the method raising an exception or not.

from ansible_collections.community.general.plugins.module_utils.module_helper import cause_changes

# adapted excerpt from the community.general.jira module
class JIRA(StateModuleHelper):
    @cause_changes(when="success")
    def operation_create(self):
        ...

If when has a different value or no parameters are specificied, the decorator will have no effect whatsoever.

@module_fails_on_exception

In a method using this decorator, if an exception is raised, the text message of that exception will be captured by the decorator and used to call self.module.fail_json(). In most of the cases there will be no need to use this decorator, because ModuleHelper.run() already uses it.

@check_mode_skip

If the module is running in check mode, this decorator will prevent the method from executing. The return value in that case is None.

from ansible_collections.community.general.plugins.module_utils.module_helper import check_mode_skip

# adapted excerpt from the community.general.locale_gen module
class LocaleGen(StateModuleHelper):
    @check_mode_skip
    def __state_fallback__(self):
        ...

@check_mode_skip_returns

This decorator is similar to the previous one, but the developer can control the return value for the method when running in check mode. It is used with one of two parameters. One is callable and the return value in check mode will be callable(self, *args, **kwargs), where self is the ModuleHelper instance and the union of args and kwargs will contain all the parameters passed to the method.

The other option is to use the parameter value, in which case the method will return value when in check mode.

References

Added in version 3.1.0.