模块助手指南
简介
编写 Ansible 模块在很大程度上已在现有文档中描述。但是,其中很大一部分是样板代码,每次都需要重复。这就是 ModuleHelper
发挥作用的地方:它完成了大量的样板代码。
快速入门
请参阅 Ansible 文档中的示例,该示例使用 ModuleHelper
编写。但请记住,它没有展示 MH 的所有功能。
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()
模块助手
简介
ModuleHelper
是标准 AnsibleModule
的包装器,提供额外的功能和便利性。使用 ModuleHelper
的模块的基本结构如上面的 快速入门 部分所示,但还有更多元素将参与其中。
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(...),
# ...
)
导入 ModuleHelper
类后,您需要声明自己的类来扩展它。
另请参阅
有一个名为 StateModuleHelper
的变体,它建立在 MH 提供的功能之上。有关详细信息,请参阅下面的 StateModuleHelper。
指定模块的最简单方法是创建类变量 module
,其中包含将作为参数传递给 AnsibleModule
的确切参数的字典。如果您喜欢自己创建 AnsibleModule
对象,只需将其分配给 module
类变量。如果使用了该参数,MH 还在其构造函数中接受参数 module
,那么它将覆盖类变量。该参数也可以是 dict
或 AnsibleModule
。
除了模块的定义之外,还有其他变量可用于控制 MH 行为的各个方面。这些变量应在类开始时设置,其语义将在本文档中解释。
MH 的主要逻辑发生在 ModuleHelper.run()
方法中,如下所示
@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)
方法 ModuleHelper.__run__()
必须由模块实现,大多数模块将能够通过仅实现该 MH 方法来执行其操作。但是,在某些情况下,您可能希望在主要任务之前或之后执行操作,在这种情况下,您应该分别实现 ModuleHelper.__init_module__()
和 ModuleHelper.__quit_module__()
。
请注意,输出来自 self.output
,这是一个 @property
方法。默认情况下,该属性将收集所有标记为输出的变量,并以带有其值的字典形式返回它们。此外,默认的 self.output
还将处理 Ansible facts
和*差异模式*。另请注意,更改的状态来自 self.has_changed()
,它通常根据标记为跟踪其内容更改的变量来计算。
另请参阅
请参阅下面有关装饰器 @module_fails_on_exception 的更多信息。
编写 快速入门 中的示例的另一种方法是
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")
请注意,没有对 module.exit_json()
或 module.fail_json()
的调用:如果模块失败,则引发异常。您可以使用便捷方法 self.do_raise()
或像在 Python 中一样正常引发异常来执行此操作。如果没有引发异常,则模块成功。
另请参阅
有关异常的更多信息,请参阅下面的 异常 部分。
Ansible 模块必须具有 main()
函数和通常的 '__main__'
测试。使用 MH 时,应如下所示
def main():
MyTest.execute()
if __name__ == '__main__':
main()
类方法 execute()
只不过是以下内容的便捷快捷方式
m = MyTest()
m.run()
(可选)可以将 AnsibleModule
作为参数传递给 execute()
。
参数、变量和输出
所有参数都会自动成为 self.vars
属性中的变量,该属性的类型为 VarDict
。通过使用 self.vars
,您将获得一个中心机制来访问参数,同时也能够将变量作为模块的返回值公开。正如 VarDict 指南 中所述,VarDict
中的变量具有与之关联的元数据。该元数据中的一个属性会标记变量以进行输出,而 MH 会利用该属性来生成模块的返回值。
重要提示
上述 VarDict
功能是在 community.general 7.1.0 中引入的,但最初有一个嵌入在 ModuleHelper
中的实现。该较旧的实现现在已被弃用,将在 community.general 11.0.0 中移除。在 community.general 7.1.0 之后,MH 模块会生成关于使用旧的 VarDict 的弃用消息。有两种方法可以防止这种情况发生
设置
mute_vardict_deprecation = True
,则弃用消息将被静音。如果模块仍然使用旧的VarDict
,则在 community.general 11.0.0(2026 年春季)发布时,它将无法更新。设置
use_old_vardict = False
,使 MH 模块立即使用新的VarDict
。新的VarDict
及其用法已记录在案,这是推荐的处理方式。
class MyTest(ModuleHelper):
use_old_vardict = False
mute_vardict_deprecation = True
...
这两个设置是互斥的,但没有强制执行,并且设置两者时的行为未指定。
与在 VarDict
中创建的新变量相反,默认情况下模块参数不会设置为输出。如果您想在输出中包含某些模块参数,请将它们列在 output_params
类变量中。
class MyTest(ModuleHelper):
output_params = ('state', 'name')
...
MH 通过使用 VarDict
提供的另一个巧妙功能是在设置元数据 change=True
时自动跟踪更改。同样,要为模块参数启用此功能,您必须将它们列在 change_params
类变量中。
class MyTest(ModuleHelper):
# example from community.general.xfconf
change_params = ('value', )
...
另请参阅
请参阅下面的 处理更改 以了解更多信息。
类似地,如果您想使用 Ansible 的 diff 模式,您可以为模块参数设置元数据 diff=True
和 diff_params
。这样,MH 将自动为已更改的变量生成 diff 输出。
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)
此外,如果模块设置为返回事实而不是返回值,则再次为模块参数使用元数据 fact=True
和 fact_params
。此外,您必须指定 facts_name
,如下所示
class VolumeFacts(ModuleHelper):
facts_name = 'volume_facts'
def __init_module__(self):
self.vars.set("volume", 123, fact=True)
这将生成一个类似以下的 Ansible 事实
- name: Obtain volume facts
some.collection.volume_facts:
# parameters
- name: Print volume facts
debug:
msg: Volume fact is {{ ansible_facts.volume_facts.volume }}
重要提示
如果未设置 facts_name
,则该模块不会生成任何事实。
处理更改
在 MH 中,有很多方法可以指示模块执行中的更改。它们如下
跟踪变量中的更改
如上所述,您可以在 self.vars
中启用任意数量变量的更改跟踪。在模块执行结束时,如果这些变量中的任何一个的值与分配给它们的第一个值不同,那么这将由 MH 拾取,并在模块输出中标记为已更改。请参阅下面的示例,了解如何在变量中启用更改跟踪
# 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=[])
...
如果任何标记为 change
的变量的最终值与其初始值不同,则 MH 将返回 changed=True
。
使用 changed
指示更改
如果您想在代码中直接指示更改,请使用 MH 中的 self.changed
属性。请注意,这是 MH 中的一个 @property
方法,它同时具有 getter 和 setter。默认情况下,该隐藏字段设置为 False
。
有效更改
模块的有效结果在 self.has_changed()
方法中确定,它由 self.changed
和从 self.vars
计算的更改之间的逻辑 OR 运算组成。
异常
在 MH 中,您可以直接引发异常,而不是调用 module.fail_json()
。输出变量的收集方式与成功执行时相同。但是,如果您选择,您可以专门为该异常设置输出变量。
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})
所有从 Exception
派生的异常都会被捕获并转换为 fail_json()
调用。但是,如果您确实想自己调用 self.module.fail_json()
,它也会起作用,只是请记住,在这种情况下不会自动处理输出变量。
StateModuleHelper
许多模块使用一个参数 state
,它有效地控制模块执行的确切操作,例如 state=present
或 state=absent
用于安装或删除软件包。通过使用 StateModuleHelper
,您可以使您的代码像下面 gconftool2
中的摘录一样
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
请注意,方法 __run__()
是在 StateModuleHelper
中实现的,您只需要实现方法 state_<state_value>
。在上面的示例中,community.general.gconftool2 只有两个状态,present
和 absent
,因此,有 state_present()
和 state_absent()
。
如果控制参数不叫做 state
,就像在 community.general.jira 模块中一样,只需让 SMH 知道即可
class JIRA(StateModuleHelper):
state_param = 'operation'
def operation_create(self):
...
def operation_search(self):
...
最后,如果使用 state=somevalue
调用模块并且未实现方法 state_somevalue
,则 SMH 将回退到调用一个名为 __state_fallback__()
的方法。默认情况下,此方法将引发一个 ValueError
,指示未找到该方法。当然,您可以重写该方法以编写默认实现,如 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)
该模块只有 present
和 absent
两种状态,并且两者的代码都在回退方法中。
注意
如果您设置了不同的 state_param
值,回退方法的名称不会改变。
其他便利功能
委托给 AnsibleModule
下面的 MH 属性和方法会按原样委托给 self.module
中的底层 AnsibleModule
实例
check_mode
get_bin_path()
warn()
deprecate()
此外,MH 还会委托
diff_mode
到self.module._diff
verbosity
到self.module._verbosity
装饰器
以下装饰器应仅在 ModuleHelper
类中使用。
@cause_changes
此装饰器将控制方法的执行结果是否会导致模块在其输出中发出更改信号。如果方法完成时没有引发异常,则认为它已成功,否则,它将失败。
该装饰器有一个参数 when
,它接受三个不同的值:success
、failure
和 always
。还有两个遗留参数 on_success
和 on_failure
,它们将被弃用,因此请不要使用它们。模块输出中的 changed
值将设置为 True
when="success"
且该方法完成时没有引发异常。when="failure"
且该方法引发异常。when="always"
,无论该方法是否引发异常。
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):
...
如果 when
具有不同的值或未指定参数,则装饰器将不起任何作用。
@module_fails_on_exception
在使用此装饰器的方法中,如果引发异常,该异常的文本消息将被装饰器捕获,并用于调用 self.module.fail_json()
。在大多数情况下,不需要使用此装饰器,因为 ModuleHelper.run()
已经使用了它。
@check_mode_skip
如果模块在检查模式下运行,此装饰器将阻止方法执行。在这种情况下,返回值是 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
此装饰器与前一个类似,但开发人员可以控制在检查模式下运行时该方法的返回值。它与两个参数之一一起使用。一个是 callable
,检查模式下的返回值将是 callable(self, *args, **kwargs)
,其中 self
是 ModuleHelper
实例,args
和 kwargs
的并集将包含传递给该方法的所有参数。
另一个选项是使用参数 value
,在这种情况下,当处于检查模式时,该方法将返回 value
。
参考
3.1.0 版本新增。