Ansible 模块架构

如果您正在研究 ansible-core 代码、编写 Ansible 模块或开发动作插件,您可能需要了解 Ansible 的程序流是如何执行的。如果您只是在 playbook 中使用 Ansible 模块,可以跳过本节。

模块类型

Ansible 的代码库支持几种不同类型的模块。其中一些是为了向后兼容,另一些是为了提供灵活性。

动作插件

对于编写 playbook 的人来说,动作插件看起来就像是模块。大多数动作插件的使用文档都存在于同名的模块中。有些动作插件负责完成所有工作,而对应的模块仅提供文档。有些动作插件负责执行模块。normal 动作插件执行那些没有特殊动作插件的模块。动作插件始终在控制节点上执行。

某些动作插件完全在控制节点上工作。例如,debug 动作插件(打印文本供用户查看)和 assert 动作插件(测试 playbook 中的值是否满足特定条件)完全在控制节点上执行。

大多数动作插件在控制节点上设置一些值,然后调用受管节点上的实际模块来处理这些值。例如,template 动作插件获取用户提供的值,并使用 playbook 环境中的变量在控制节点的临时位置构建一个文件。接着,它将该临时文件传输到远程系统上的临时文件中。之后,它会调用在远程系统上运行的 copy 模块,将文件移动到其最终位置、设置文件权限等。

新式模块

Ansible 随附的所有模块都属于此类。虽然您可以使用任何语言编写模块,但所有官方模块(随 Ansible 一起发布)都使用 Python 或 PowerShell。

新式模块以某种方式将模块参数嵌入在自身内部。而老式模块必须将一个单独的文件复制到受管节点,这效率较低,因为它需要两次网络连接,而新式模块只需要一次。

Python

新式 Python 模块使用 Ansiballz 框架 构建。这些模块通过导入 ansible.module_utils 来引入样板模块代码,例如参数解析、将返回值格式化为 JSON 以及各种文件操作。

注意

在 Ansible 2.0.x 及更早版本中,官方 Python 模块使用的是 Module Replacer 框架。对于模块作者来说,Ansiballz 框架 很大程度上是 Module Replacer 框架 功能的超集,因此您通常不需要了解它们之间的区别。

PowerShell

新式 PowerShell 模块使用 Module Replacer 框架 构建。这些模块在发送到受管节点之前,会在其中嵌入一个 PowerShell 代码库。

JSONARGS 模块

这些模块是其主体中包含字符串 <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> 的脚本。该字符串会被替换为 JSON 格式的参数字符串。这些模块通常会像这样将一个变量设置为该值

json_arguments = """<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"""

它会被展开为

json_arguments = """{"param1": "test's quotes", "param2": "\"To be or not to be\" - Hamlet"}"""

注意

Ansible 输出一个带有裸引号的 JSON 字符串。双引号用于引用字符串值,字符串值内部的双引号使用反斜杠转义,而单引号在字符串值内部可能以未转义形式出现。要使用 JSONARGS,您的脚本语言必须能够处理这种类型的字符串。示例中使用 Python 的三引号字符串来实现此目的。其他脚本语言可能具有类似的引号字符,不会与 JSON 中的任何引号混淆,或者可能允许您定义自己的起始引号和结束引号字符。如果语言没有提供这些功能,您则需要编写 非原生 JSON 模块老式模块

这些模块通常使用 JSON 库解析 json_arguments 的内容,然后在整个代码中将其作为原生变量使用。

非原生 want JSON 模块

如果一个模块在任何位置包含字符串 WANT_JSON,Ansible 就会将其视为非原生模块,该模块仅接受一个文件名作为命令行参数。该文件名指向一个包含模块参数 JSON 字符串的临时文件。该模块需要打开该文件,读取并解析参数,对数据进行操作,并在退出前将返回的数据作为 JSON 编码的字典打印到 stdout。

这些类型的模块是自包含的实体。自 Ansible 2.1 起,Ansible 仅会在它们存在 shebang 行时对其进行修改以更改 shebang 行。

另请参阅

Ansible for Rubyists 仓库中可以找到用 Ruby 编写的非原生模块示例。

二进制模块

从 Ansible 2.2 开始,模块也可以是小型二进制程序。Ansible 不会执行任何特殊处理来使它们可以在不同系统之间移植,因此它们可能特定于编译它们的系统,或者需要其他二进制运行时依赖项。尽管有这些缺点,但如果这是访问某些资源的唯一方法,您可能必须针对特定的二进制库编译自定义模块。

二进制模块获取参数并将数据返回给 Ansible 的方式与 want JSON 模块 相同。

另请参阅

一个用 Go 编写的二进制模块示例。

老式模块

老式模块与 want JSON 模块 类似,不同之处在于它们获取的文件包含参数的 key=value 键值对,而不是 JSON。当模块没有任何能表明其属于其他类型的标记时,Ansible 就会判定该模块为老式模块。

模块是如何执行的

当用户使用 ansibleansible-playbook 时,他们会指定一个要执行的任务。该任务通常是模块的名称以及要传递给该模块的若干参数。Ansible 会获取这些值并以各种方式对其进行处理,然后才最终在远程机器上执行。

Executor/task_executor

TaskExecutor 接收从 playbook(或者在 /usr/bin/ansible 的情况下从命令行)解析的模块名称和参数。它使用该名称来判断当前面对的是模块还是 动作插件 (Action Plugin)。如果是模块,它会加载 普通动作插件 (Normal Action Plugin),并将名称、变量以及关于任务和 play 的其他信息传递给该动作插件,以便进一步处理。

The normal 动作插件

normal 动作插件在远程主机上执行模块。它是协调在受管机器上实际执行模块的大部分工作的主要协作者。

  • 它会为任务加载适当的连接插件,然后该插件根据需要进行传输或执行以建立与该主机的连接。

  • 它会将所有内部 Ansible 属性添加到模块的参数中(例如,将 no_log 传递给模块的属性)。

  • 它与其他插件(连接、shell、become、其他动作插件)配合,在远程机器上创建任何临时文件,并在之后进行清理。

  • 它将模块和模块参数推送到远程主机,尽管下一节中描述的 module_common 代码决定了它们将采用哪种格式。

  • 它处理与模块相关的任何特殊情况(例如,异步执行,或者 Windows 模块必须与 Python 模块同名所带来的复杂性,以便其他动作插件内部调用模块时能正常工作。)

这些功能大部分来自 BaseAction 类,该类位于 plugins/action/__init__.py 中。它使用 ConnectionShell 对象来完成其工作。

注意

当使用 async: 参数运行 任务 时,Ansible 使用 async 动作插件而不是 normal 动作插件来调用它。该程序流目前尚未记录在文档中。请阅读源码以获取其工作原理的信息。

Executor/module_common.py

executor/module_common.py 中的代码负责组装要发送到受管节点的模块。首先读取模块,然后对其进行检测以确定其类型

在组装步骤之后,会对所有具有 shebang 行的模块进行最后一次修改。Ansible 会检查 shebang 行中的解释器是否通过 ansible_$X_interpreter 主机清单变量配置了特定路径。如果是这样,Ansible 会用该路径替换模块中给出的解释器路径。此后,Ansible 将完整的模块数据和模块类型返回给 普通动作 (Normal Action) 插件,后者继续执行模块。

组装器框架

Ansible 支持两种组装器框架:Ansiballz 和较旧的 Module Replacer。

Module Replacer 框架

Module Replacer 框架是实现新式模块的原始框架,目前仍用于 PowerShell 模块。它本质上是一个预处理器(对于熟悉该编程语言的人来说,类似于 C 预处理器)。它对模块文件中的特定子字符串模式进行直接替换。有两种替换类型

  • 仅在模块文件中进行的替换。这些是公共替换字符串,模块可以利用它们来获取有用的样板代码或访问参数。

    • from ansible.module_utils.MOD_LIB_NAME import * 会被替换为 ansible/module_utils/MOD_LIB_NAME.py 的内容。这应该仅与 新式 Python 模块 一起使用。

    • #<<INCLUDE_ANSIBLE_MODULE_COMMON>> 等同于 from ansible.module_utils.basic import *,并且也应该仅适用于新式 Python 模块。

    • # POWERSHELL_COMMON 替换 ansible/module_utils/powershell.ps1 的内容。它应该仅与 新式 Powershell 模块 一起使用。

  • ansible.module_utils 代码使用的替换。这些是内部替换模式。它们可能会在内部、以及上述公共替换中使用,但不应由模块直接使用。

    • "<<ANSIBLE_VERSION>>" 会被替换为 Ansible 版本。在 Ansiballz 框架 下的 新式 Python 模块 中,正确的方法是实例化一个 AnsibleModule,然后从 :attr:AnsibleModule.ansible_version 访问该版本。

    • "<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>" 会被替换为表示 Python reprJSON 编码模块参数的字符串。在 JSON 字符串上使用 repr 可以使其安全地嵌入到 Python 文件中。在 Ansiballz 框架下的新式 Python 模块中,最好通过实例化一个 AnsibleModule,然后使用 AnsibleModule.params 来访问它。

    • <<SELINUX_SPECIAL_FILESYSTEMS>> 会替换为一个字符串,该字符串是在 SELinux 中具有文件系统依赖安全上下文的文件系统的逗号分隔列表。在新式 Python 模块中,如果您确实需要此内容,则应实例化一个 AnsibleModule,然后使用 AnsibleModule._selinux_special_fs。该变量也已从以逗号分隔的文件系统名称字符串更改为实际的 Python 文件系统名称列表。

    • <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> 将模块参数替换为 JSON 字符串。必须注意正确引用该字符串,因为 JSON 数据可能包含引号。此模式不会在新式 Python 模块中进行替换,因为它们可以通过其他方式获取模块参数。

    • 出现字符串 syslog.LOG_USER 的地方都会被替换为在 ansible.cfg 中指定的 syslog_facility,或适用于此主机的任何 ansible_syslog_facility 主机清单变量。在新式 Python 模块中,此处的行为略有改变。如果您确实需要访问它,则应实例化一个 AnsibleModule,然后使用 AnsibleModule._syslog_facility 来访问它。它不再是实际的 syslog 设施,而是 syslog 设施的名称。有关详细信息,请参阅内部参数文档

Ansiballz 框架

Ansiballz 框架于 Ansible 2.1 中被采用,并用于所有新式 Python 模块。与 Module Replacer 不同,Ansiballz 使用对 ansible/module_utils 中内容的真实 Python 导入,而不是仅仅对模块进行预处理。它通过构建一个 zip 文件来实现这一点——该文件包含模块文件、被该模块导入的 ansible/module_utils 中的文件,以及一些用于传递模块参数的样板代码。然后将该 zip 文件进行 Base64 编码,并包裹在一个小型 Python 脚本中,该脚本负责解码 Base64 编码并将 zip 文件放置在受管节点的临时目录中。接着,它从 zip 文件中仅解压出 Ansible 模块脚本,并将其同样放入临时目录。然后它设置 PYTHONPATH 以查找 zip 文件内部的 Python 模块,并以特殊名称 __main__ 导入该 Ansible 模块。将其作为 __main__ 导入会使 Python 认为它是在执行脚本,而不仅仅是导入模块。这使得 Ansible 能够在远程机器上的单个 Python 副本中同时运行包装器脚本和模块代码。

注意

  • Ansible 将 zip 文件包装在 Python 脚本中,原因有两个:

    • 兼容 Python 2.6,因为该版本的 Python -m 命令行开关功能较弱。

    • 确保 pipelining (流水线) 正常运行。Pipelining 需要将 Python 模块通过管道传输到远程节点上的 Python 解释器。Python 能够理解 stdin 上的脚本,但无法理解 zip 文件。

  • 在 Ansible 2.7 之前,模块是由第二个 Python 解释器执行的,而不是在同一个进程中执行。在放弃对 Python 2.4 的支持后做出了这一改变,以加速模块执行。

在 Ansiballz 中,任何从 ansible.module_utils 包导入 Python 模块的操作都会触发将该 Python 文件包含到 zip 文件中。模块中出现的 #<<INCLUDE_ANSIBLE_MODULE_COMMON>> 会被转换为 from ansible.module_utils.basic import *,然后 ansible/module-utils/basic.py 会被包含在 zip 文件中。从 module_utils 引入的文件本身也会被扫描,以检测是否导入了 module_utils 中的其他 Python 模块,以便同样包含在 zip 文件中。

传递参数

这两个框架传递参数的方式不同:

  • Module Replacer 框架 中,模块参数被转换为 JSON 化的字符串,并替换到组合后的模块文件中。

  • Ansiballz 框架 中,JSON 化的字符串是包装 zip 文件的脚本的一部分。在包装器脚本以 __main__ 导入 Ansible 模块之前,它会使用这些变量值对 basic.py 中的私有变量 _ANSIBLE_ARGS 进行猴子补丁(monkey-patch)操作。当实例化 ansible.module_utils.basic.AnsibleModule 时,它会解析此字符串并将参数放入 AnsibleModule.params 中,供模块的其他代码访问。

警告

如果您正在编写模块,请记住,我们传递参数的方式是内部实现细节:它在过去曾发生过改变,并且一旦公共 module_utils 代码的更改允许 Ansible 模块放弃使用 ansible.module_utils.basic.AnsibleModule 时,它还会再次改变。请不要依赖内部全局变量 _ANSIBLE_ARGS

非常动态的自定义模块(在实例化 AnsibleModule 之前需要解析参数)可以使用 _load_params 来检索这些参数。尽管为了支持代码更改,_load_params 可能会发生破坏性的改变,但它可能比我们传递参数的方式或内部全局变量都更加稳定。

注意

在 Ansible 2.7 之前,Ansible 模块是在第二个 Python 解释器中被调用的,然后通过该脚本的 stdin 将参数传递给脚本。

内部参数

Module Replacer 框架Ansiballz 框架 都会向 Ansible 模块发送除用户在 playbook 中指定的参数之外的其他参数。这些附加参数是用于帮助实现 Ansible 全局功能的内部参数。模块通常不需要显式地了解这些参数,因为这些功能是在 ansible.module_utils.basic 中实现的。然而,某些功能需要模块的支持,并且了解一些内部参数会很有用。

本节中的内部参数是全局参数。如果您需要向自定义模块添加本地内部参数,请为该特定模块创建动作插件。可以参考 copy 动作插件 中的 _original_basename 作为示例。

_ansible_no_log

类型:bool

每当任务或 play 中的参数指定了 no_log 时,该参数就会被设置为 True。任何调用 AnsibleModule.log() 函数的模块都会自动处理此操作。如果您的模块实现了自己的日志记录,那么您需要检查 _ansible_no_log 的值。要在模块中访问 _ansible_no_log,请实例化 AnsibleModule 实用程序,然后检查 AnsibleModule.no_log 的值。

注意

模块的 argument_spec 中指定的 no_log 由不同的机制处理。

_ansible_debug

类型:bool

控制详细日志记录以及模块执行的外部命令的日志记录。如果模块使用的是 AnsibleModule.debug() 函数而不是 AnsibleModule.log() 函数,那么只有在您将 _ansible_debug 参数设置为 True 时才会记录消息。要在模块中访问 _ansible_debug,请实例化 AnsibleModule 实用程序并访问 AnsibleModule._debug。有关更多详细信息,请参阅 DEFAULT_DEBUG

_ansible_diff

类型:bool

通过此参数,您可以配置模块以显示将应用于模板化文件的更改的统一差异 (unified diff)。要在模块中访问 _ansible_diff,请实例化 AnsibleModule 实用程序并访问 AnsibleModule._diff。您还可以使用 playbook 中的 diff 关键字或相关的环境变量来访问此参数。有关更多详细信息,请参阅 Playbook 关键字DIFF_ALWAYS 配置选项。

_ansible_verbosity

类型:int

您可以使用此参数控制日志记录的详细程度级别(0 表示无)。

_ansible_selinux_special_fs

类型:list,元素:strings

该参数向模块提供应具有特殊 SELinux 上下文的文件系统的名称。它们被用于对文件进行操作(更改属性、移动和复制)的 AnsibleModule 方法。

大多数模块可以使用内置的 AnsibleModule 方法来操作文件。如果要在需要了解这些特殊上下文文件系统的模块中访问此内容,请实例化 AnsibleModule 并检查 AnsibleModule._selinux_special_fs 中的列表。

此参数取代了来自 Module Replacer 框架ansible.module_utils.basic.SELINUX_SPECIAL_FS。在 Module Replacer 框架中,该参数被格式化为以逗号分隔的文件系统名称字符串。在 Ansiballz 框架下,它是一个列表。您可以使用相应的环境变量访问 _ansible_selinux_special_fs。有关更多详细信息,请参阅 DEFAULT_SELINUX_SPECIAL_FS 配置选项。

2.1 版本新功能。

_ansible_syslog_facility

该参数控制模块记录日志到的 syslog 设施 (facility)。大多数模块只需使用 AnsibleModule.log() 函数,该函数会自动利用此参数。如果模块必须自行使用此参数,它应该实例化 AnsibleModule 方法,然后从 AnsibleModule._syslog_facility 检索 syslog 设施的名称。Ansiballz 代码没有 Module Replacer 框架 代码那么优雅

# Old module_replacer way
import syslog
syslog.openlog(NAME, 0, syslog.LOG_USER)

# New Ansiballz way
import syslog
facility_name = module._syslog_facility
facility = getattr(syslog, facility_name, syslog.LOG_USER)
syslog.openlog(NAME, 0, facility)

有关更多详细信息,请参阅 DEFAULT_SYSLOG_FACILITY 配置选项。

2.1 版本新功能。

_ansible_version

此参数将 Ansible 的版本传递给模块。要访问它,模块应实例化 AnsibleModule 方法,然后从 AnsibleModule.ansible_version 检索该版本。这取代了来自 Module Replacer 框架ansible.module_utils.basic.ANSIBLE_VERSION

2.1 版本新功能。

_ansible_module_name

类型:str

此参数向模块传递关于其名称的信息。有关更多详细信息,请参阅配置选项 DEFAULT_MODULE_NAME

_ansible_keep_remote_files

类型:bool

该参数提供了指示,说明模块在需要保留远程文件时必须做好准备。有关更多详细信息,请参阅 DEFAULT_KEEP_REMOTE_FILES 配置选项。

_ansible_socket

此参数为模块提供了一个用于持久连接的套接字。该参数是使用 PERSISTENT_CONTROL_PATH_DIR 配置选项创建的。

_ansible_shell_executable

类型:bool

此参数确保模块使用指定的 shell 可执行文件。有关更多详细信息,请参阅 ansible_shell_executable 远程主机环境参数。

_ansible_tmpdir

类型:str

该参数指示模块,所有命令必须使用指定的临时目录(如果已创建)。动作插件设计了这个临时目录。

模块可以通过使用公共的 tmpdir 属性来访问该参数。如果动作插件没有设置该参数,tmpdir 属性将创建一个临时目录。

目录名称是随机生成的,且目录的根目录由以下各项之一决定:

因此,使用 ansible.cfg 配置文件来激活或自定义此设置,并不能保证您可以控制完整的值。

_ansible_remote_tmp

如果动作插件没有设置 _ansible_tmpdir,模块的 tmpdir 属性会在该目录中创建一个随机的目录名称。有关更多详细信息,请参阅 shell 插件的 remote_tmp 参数。

模块返回值与不安全字符串

在模块执行结束时,它会将其想要返回的数据格式化为 JSON 字符串,并将该字符串打印到其 stdout。普通动作插件接收该 JSON 字符串,将其解析为 Python 字典,并将其返回给执行器(executor)。

如果 Ansible 模板化了每个字符串返回值,它将容易受到来自有权访问受管节点的用户的攻击。如果无耻的用户将恶意代码伪装成 Ansible 返回值字符串,而这些字符串随后在控制节点上进行了模板化,Ansible 就可能会执行任意代码。为了防止这种情况,Ansible 将返回数据中的所有字符串标记为 Unsafe(不安全),从而原样输出字符串中的任何 Jinja2 模板,而不由 Jinja2 进行展开。

通过 ActionPlugin._execute_module() 调用模块返回的字符串会自动被普通动作插件标记为 Unsafe。如果另一个动作插件通过其他方式从模块中检索信息,它必须自行将其返回数据标记为 Unsafe

如果一个编写不良的动作插件未能将其结果标记为“Unsafe”,Ansible 会在结果返回到执行器时再次对其进行审计,将所有字符串标记为 Unsafe。普通动作插件通过以结果数据作为参数来保护自身以及它所调用的任何其他代码。执行器内部的检查保护了所有其他动作插件的输出,从而确保 Ansible 运行的后续任务也不会从这些结果中模板化任何内容。

特殊注意事项

Pipelining

Ansible 可以通过以下两种方式之一将模块传输到远程机器:

  • 它可以将模块写入远程主机上的临时文件,然后利用与远程主机的第二次连接,使用模块所需的解释器来执行它

  • 或者,它可以使用所谓的 pipelining(流水线),通过将模块用管道输送到远程解释器的 stdin 中来执行模块。

目前 pipelining 仅适用于使用 Python 编写的模块,因为 Ansible 仅知道 Python 支持这种操作模式。支持 pipelining 意味着,无论模块有效负载在通过网络发送之前采用何种格式,它都必须能够由 Python 通过 stdin 执行。

为什么通过 stdin 传递参数?

选择通过 stdin 传递参数的原因如下:

  • 当与 ANSIBLE_PIPELINING 结合使用时,这可以防止模块的参数临时保存到远程机器的磁盘上。这使得远程机器上的恶意用户更难(但非不可能)窃取参数中可能存在的任何敏感信息。

  • 命令行参数是不安全的,因为大多数系统允许非特权用户读取进程的完整命令行。

  • 环境变量通常比命令行更安全,但某些系统限制了环境的总大小。如果我们达到该限制,这可能会导致参数被截断。

AnsibleModule

参数规范

提供给 AnsibleModuleargument_spec 定义了模块支持的参数,以及它们的类型、默认值等。

argument_spec 示例

module = AnsibleModule(argument_spec=dict(
    top_level=dict(
        type='dict',
        options=dict(
            second_level=dict(
                default=True,
                type='bool',
            )
        )
    )
))

本节将讨论参数的行为属性

type:

type 允许您定义参数接受的值的类型。type 的默认值是 str。可能的值有:

  • str

  • list

  • dict

  • bool

  • int

  • float

  • path

  • raw

  • jsonarg

  • json

  • bytes

  • bits

raw 类型不执行任何类型验证或类型转换,并保持所传递的值的类型。

elements:

elementstype='list' 时与 type 结合使用。之后可以将 elements 定义为 elements='int' 或任何其他类型,表示指定列表中的每个元素都应该属于该类型。

default:

default 选项允许在未向模块提供参数的情况下为该参数设置默认值。如果未指定,默认值为 None

fallback:

fallback 接受一个 tuple(元组),其中第一个参数是一个可调用对象(函数),它将基于第二个参数执行查找。第二个参数是该可调用对象可接受的值的列表。

最常用的可调用对象是 env_fallback,它允许在未提供参数时让参数可选地使用环境变量。

示例

username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']))
choices:

choices 接受一个该参数可接受的选择列表。choices 的类型应与 type 匹配。

required:

required 接受一个布尔值,TrueFalse,表示该参数是必需的。未指定时,required 默认为 False。不应将其与 default 结合使用。

no_log:

no_log 接受一个布尔值,TrueFalse,用于显式指出是否应在日志和输出中屏蔽该参数值。

注意

在没有设置 no_log 的情况下,如果参数名称似乎表明参数值是一个密码或口令(例如 “admin_password”),则会显示一个警告,且该值在日志中会被屏蔽,但在输出中不会屏蔽。要禁用针对不含敏感信息的参数的警告和屏蔽,请将 no_log 设置为 False

aliases:

aliases 接受该参数的候选参数名称列表,例如参数为 name 但模块接受 aliases=['pkg'],以允许 pkgname 互换使用。使用别名可能会使模块接口令人困惑,因此我们建议仅在必要时添加它们。如果您正在更新参数名称以修复拼写错误或改进接口,请考虑将旧名称移至 deprecated_aliases,而不是无限期地保留它们。

options:

options 实现了创建子参数规范(sub-argument_spec)的能力,其中顶层参数的子选项也使用本节讨论的属性进行验证。本节顶部的示例演示了 options 的使用。在这种情况下,typeelements 应该为 dict

apply_defaults:

apply_defaultsoptions 协同工作,并且允许在不提供顶级参数的情况下也应用子选项的 default 值。

在本节顶部的 argument_spec 示例中,即使调用模块时用户没有提供 top_level,它也会允许定义 module.params['top_level']['second_level']

removed_in_version:

removed_in_version 指示已弃用的参数将在哪个版本的 ansible-core 或集合中被移除。与 removed_at_date 互斥,并且必须与 removed_from_collection 一起使用。

示例

option = {
  'type': 'str',
  'removed_in_version': '2.0.0',
  'removed_from_collection': 'testns.testcol',
},
removed_at_date:

removed_at_date 指示已弃用的参数将在此日期之后的 ansible-core 次要版本或集合主要版本中被移除。与 removed_in_version 互斥,并且必须与 removed_from_collection 一起使用。

示例

option = {
  'type': 'str',
  'removed_at_date': '2020-12-31',
  'removed_from_collection': 'testns.testcol',
},
removed_from_collection:

指定哪个集合(或 ansible-core)弃用了此弃用参数。对于 ansible-core 请指定 ansible.builtin,或者指定集合名称(格式为 foo.bar)。必须与 removed_in_versionremoved_at_date 一起使用。

deprecated_aliases:

弃用该参数的别名。必须包含具有以下某些键的字典的列表或元组:

name:

要弃用的别名的名称。(必需。)

version:

此别名将在其中被移除的 ansible-core 或集合的版本。必须指定 versiondate 之一。

date:

在此日期之后,ansible-core 的次要版本或集合的主要版本将不再包含此别名。必须指定 versiondate 之一。

collection_name:

指定哪个集合(或 ansible-core)弃用了此弃用别名。对于 ansible-core 请指定 ansible.builtin,或者指定集合名称(格式为 foo.bar)。必须与 versiondate 一起使用。

示例

option = {
  'type': 'str',
  'aliases': ['foo', 'bar'],
  'deprecated_aliases': [
    {
      'name': 'foo',
      'version': '2.0.0',
      'collection_name': 'testns.testcol',
    },
    {
      'name': 'foo',
      'date': '2020-12-31',
      'collection_name': 'testns.testcol',
    },
  ],
},
mutually_exclusive:

如果指定了 optionsmutually_exclusive 指的是 options 中描述的子选项,并且其行为与模块选项之间的依赖关系相同。

required_together:

如果指定了 optionsrequired_together 指的是 options 中描述的子选项,并且其行为与模块选项之间的依赖关系相同。

required_one_of:

如果指定了 optionsrequired_one_of 指的是 options 中描述的子选项,并且其行为与模块选项之间的依赖关系相同。

required_if:

如果指定了 optionsrequired_if 指的是 options 中描述的子选项,并且其行为与模块选项之间的依赖关系相同。

required_by:

如果指定了 optionsrequired_by 指的是 options 中描述的子选项,并且其行为与模块选项之间的依赖关系相同。

context:

2.17 版本新功能。

您可以将 context 键的值设置为自定义内容的字典。这允许您在参数规范中提供额外的上下文。提供的内容不会被核心引擎验证或利用。

示例

option = {
    'type': 'str',
    'context': {
        'disposition': '/properties/apiType',
    },
    'choices': ['http', 'soap'],
}

模块选项之间的依赖关系

以下是 AnsibleModule() 的可选参数:

module = AnsibleModule(
  argument_spec,
  mutually_exclusive=[
    ('path', 'content'),
  ],
  required_one_of=[
    ('path', 'content'),
  ],
)
mutually_exclusive:

必须是字符串序列的序列(列表或元组)。每个字符串序列都是一组互斥的选项名称。如果同时指定了列表中的多个选项,Ansible 将使该模块因错误而运行失败。

示例

mutually_exclusive=[
  ('path', 'content'),
  ('repository_url', 'repository_filename'),
],

在此示例中,不能同时指定 pathcontent 选项。此外,也不能同时指定 repository_urlrepository_filename 选项。但是,允许同时指定 pathrepository_url

要确保正好指定两个(或多个)选项中的一个,请将 mutually_exclusiverequired_one_of 结合使用。

required_together:

必须是字符串序列的序列(列表或元组)。每个字符串序列都是必须一起指定的选项名称列表。如果指定了这些选项中的至少一个,则同一序列中的其他选项必须全部存在。

示例

required_together=[
  ('file_path', 'file_hash'),
],

在此示例中,如果指定了 file_pathfile_hash 选项之一,如果另一个未指定,Ansible 将使模块因错误而运行失败。

required_one_of:

必须是字符串序列的序列(列表或元组)。每个字符串序列都是选项名称列表,其中至少必须指定一个。如果这些选项中一个都没有指定,Ansible 将使模块执行失败。

示例

required_one_of=[
  ('path', 'content'),
],

在此示例中,必须至少指定 pathcontent 之一。如果一个都没有指定,执行将失败。明确允许同时指定两者;为防止这种情况,请将 required_one_ofmutually_exclusive 结合使用。

required_if:

必须是序列的序列。每个内部序列描述一个条件依赖关系。每个序列必须有三个或四个值。前两个值是描述该条件的选项的名称和选项的值。仅当该名称的选项正好具有此值时,才需要该序列的后续元素。

如果您希望在满足条件时指定选项名称列表中的所有选项,请使用以下形式之一:

('option_name', option_value, ('option_a', 'option_b', ...)),
('option_name', option_value, ('option_a', 'option_b', ...), False),

如果您希望在满足条件时至少指定选项名称列表中的一个选项,请使用以下形式:

('option_name', option_value, ('option_a', 'option_b', ...), True),

示例

required_if=[
  ('state', 'present', ('path', 'content'), True),
  ('force', True, ('force_reason', 'force_code')),
],

在此示例中,如果用户指定 state=present,则必须至少提供 pathcontent 选项中的一个(或两个)。为了确保只能正好指定一个,请将 required_ifmutually_exclusive 结合使用。

另一方面,如果 force(布尔参数)设置为 trueyes 等,则必须同时指定 force_reasonforce_code

required_by:

必须是映射选项名称到选项名称序列的字典。如果指定了字典键中的选项名称,则其映射到的选项名称也必须全部被指定。请注意,除了选项名称序列之外,您还可以指定单个选项名称。

示例

required_by={
  'force': 'force_reason',
  'path': ('mode', 'owner', 'group'),
},

在该示例中,如果指定了 force,则也必须指定 force_reason。另外,如果指定了 path,则还必须指定 modeownergroup 这三个选项。

声明检查模式支持

要声明模块支持检查模式,请在调用 AnsibleModule() 时提供 supports_check_mode=True

module = AnsibleModule(argument_spec, supports_check_mode=True)

模块可以通过检查布尔值 module.check_mode 来确定其是否在检查模式下被调用。如果其评估结果为 True,模块必须注意不要进行任何修改。

如果指定了 supports_check_mode=False(这是默认值),则该模块将在检查模式下退出,并显示 skipped=True 以及消息 remote module (<insert module name here>) does not support check mode

添加文件选项

要声明模块应该添加对所有常用文件选项的支持,请在调用 AnsibleModule() 时提供 add_file_common_args=True

module = AnsibleModule(argument_spec, add_file_common_args=True)

您可以在这里找到 所有文件选项的列表。在这种情况下,建议您让您的 DOCUMENTATION 继承文档片段 ansible.builtin.files(参见 文档片段),以确保所有这些字段都得到正确记录。

辅助函数 module.load_file_common_arguments()module.set_fs_attributes_if_different() 可用于为您处理这些参数

argument_spec = {
  'path': {
    'type': 'str',
    'required': True,
  },
}

module = AnsibleModule(argument_spec, add_file_common_args=True)
changed = False

# TODO do something with module.params['path'], like update its contents

# Ensure that module.params['path'] satisfies the file options supplied by the user
file_args = module.load_file_common_arguments(module.params)
changed = module.set_fs_attributes_if_different(file_args, changed)

module.exit_json(changed=changed)