模块助手指南
简介
编写 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
和 *diff 模式*。另请注意,更改的状态来自 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')
...
通过使用 VarDict
,MH 提供的另一个巧妙功能是在设置元数据 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 版本新增。