开发模块
模块是一个可重用的独立脚本,Ansible 代表您在本地或远程运行它。 模块与您的本地计算机、API 或远程系统交互,以执行特定任务,例如更改数据库密码或启动云实例。每个模块都可以被 Ansible API 或 ansible 或 ansible-playbook 程序使用。模块提供了一个定义的接口,接受参数,并通过在退出前将 JSON 字符串打印到 stdout 来向 Ansible 返回信息。
如果您需要的功能在集合中找到的数千个 Ansible 模块中都不可用,您可以轻松编写自己的自定义模块。当您编写供本地使用的模块时,您可以选择任何编程语言并遵循自己的规则。使用本主题学习如何使用 Python 创建 Ansible 模块。创建模块后,您必须将其本地添加到相应的目录中,以便 Ansible 可以找到并执行它。有关在本地添加模块的详细信息,请参阅在本地添加模块和插件。
如果您正在集合中开发模块,请参阅这些文档。
准备用于开发 Ansible 模块的环境
您只需要安装 ansible-core
即可测试模块。模块可以用任何语言编写,但以下大部分指南都假设您正在使用 Python。要包含在 Ansible 本身中的模块必须是 Python 或 Powershell。
为您的自定义模块使用 Python 或 Powershell 的一个优点是能够使用 module_utils
通用代码,该代码可以处理大量繁重的工作,包括参数处理、日志记录和响应写入等。
创建模块
强烈建议您为 Python 开发使用 venv
或 virtualenv
。
要创建模块
在您的工作区中创建一个
library
目录。您的测试 Play 应位于同一目录中。创建您的新模块文件:
$ touch library/my_test.py
。或者使用您选择的编辑器打开/创建它。将以下内容粘贴到您的新模块文件中。它包括必需的 Ansible 格式和文档、一个简单的用于声明模块选项的参数规范以及一些示例代码。
修改和扩展代码以执行您希望新模块执行的操作。请参阅编程技巧和Python 3 兼容性页面,以获取有关编写简洁明了的模块代码的提示。
#!/usr/bin/python
# Copyright: (c) 2018, Terry Jones <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: my_test
short_description: This is my test module
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "1.0.0"
description: This is my longer description explaining my test module.
options:
name:
description: This is the message to send to the test module.
required: true
type: str
new:
description:
- Control to demo if the result of this module is changed or not.
- Parameter description can be a list as well.
required: false
type: bool
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
# extends_documentation_fragment:
# - my_namespace.my_collection.my_doc_fragment_name
author:
- Your Name (@yourGitHubHandle)
'''
EXAMPLES = r'''
# Pass in a message
- name: Test with a message
my_namespace.my_collection.my_test:
name: hello world
# pass in a message and have changed true
- name: Test with a message and changed output
my_namespace.my_collection.my_test:
name: hello world
new: true
# fail the module
- name: Test failure of the module
my_namespace.my_collection.my_test:
name: fail me
'''
RETURN = r'''
# These are examples of possible return values, and in general should use other names for return values.
original_message:
description: The original name param that was passed in.
type: str
returned: always
sample: 'hello world'
message:
description: The output message that the test module generates.
type: str
returned: always
sample: 'goodbye'
'''
from ansible.module_utils.basic import AnsibleModule
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
name=dict(type='str', required=True),
new=dict(type='bool', required=False, default=False)
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
original_message='',
message=''
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
module.exit_json(**result)
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
result['original_message'] = module.params['name']
result['message'] = 'goodbye'
# use whatever logic you need to determine whether or not this module
# made any modifications to your target
if module.params['new']:
result['changed'] = True
# during the execution of the module, if there is an exception or a
# conditional state that effectively causes a failure, run
# AnsibleModule.fail_json() to pass in the message and the result
if module.params['name'] == 'fail me':
module.fail_json(msg='You requested this to fail', **result)
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
创建信息或事实模块
Ansible 使用事实模块收集有关目标计算机的信息,并使用信息模块收集有关其他对象或文件的信息。如果您发现自己尝试向现有模块添加 state: info
或 state: list
,这通常表明需要一个新的专用 _facts
或 _info
模块。
在 Ansible 2.8 及更高版本中,我们有两种类型的信息模块,它们是 *_info
和 *_facts
。
如果一个模块被命名为 <something>_facts
,则应该是因为它的主要目的是返回 ansible_facts
。不要将不执行此操作的模块命名为 _facts
。仅将 ansible_facts
用于特定于主机的信息,例如网络接口及其配置、操作系统以及安装了哪些程序。
查询/返回常规信息(而不是 ansible_facts
)的模块应命名为 _info
。常规信息是非主机特定信息,例如在线/云服务的信息(您可以从同一主机访问同一在线服务的不同帐户),或有关可从机器访问的 VM 和容器的信息,或有关单个文件或程序的信息。
信息和事实模块与任何其他 Ansible 模块一样,只有一些小的要求
它们必须命名为
<something>_info
或<something>_facts
,其中 <something> 是单数。信息
*_info
模块必须以结果字典的形式返回,以便其他模块可以访问它们。事实
*_facts
模块必须在结果字典的ansible_facts
字段中返回,以便其他模块可以访问它们。它们必须支持check_mode。
它们不能对系统进行任何更改。
您可以将您的事实添加到结果的 ansible_facts
字段中,如下所示
module.exit_json(changed=False, ansible_facts=dict(my_new_fact=value_of_fact))
其余的与创建普通模块相同。
验证您的模块代码
在您修改上面的示例代码以执行您想要的操作后,您可以尝试使用您的模块。如果您在验证模块代码时遇到错误,我们的调试提示将为您提供帮助。
在本地验证您的模块代码
最简单的方法是使用 ansible
adhoc 命令
ANSIBLE_LIBRARY=./library ansible -m my_test -a 'name=hello new=true' remotehost
如果您的模块不需要以远程主机为目标,您可以像这样快速轻松地在本地执行您的代码
ANSIBLE_LIBRARY=./library ansible -m my_test -a 'name=hello new=true' localhost
如果出于任何原因(例如pdb,使用print(),更快的迭代等)你想避免通过Ansible,另一种方法是创建一个参数文件,一个基本的 JSON 配置文件,将参数传递给你的模块,以便你可以运行它。将参数文件命名为
/tmp/args.json
并添加以下内容
{
"ANSIBLE_MODULE_ARGS": {
"name": "hello",
"new": true
}
}
然后可以在本地直接测试该模块。这会跳过打包步骤,直接使用 module_utils 文件
$ python library/my_test.py /tmp/args.json
它应该返回如下输出
{"changed": true, "state": {"original_message": "hello", "new_message": "goodbye"}, "invocation": {"module_args": {"name": "hello", "new": true}}}
在剧本中验证你的模块代码
只要 library
目录与剧本在同一目录中,你就可以通过将其包含在剧本中来轻松运行完整测试
在任何目录中创建一个剧本:
$ touch testmod.yml
将以下内容添加到新的剧本文件中
- name: test my new module
hosts: localhost
tasks:
- name: run the new module
my_test:
name: 'hello'
new: true
register: testout
- name: dump test output
debug:
msg: '{{ testout }}'
运行剧本并分析输出:
$ ansible-playbook ./testmod.yml
测试你新创建的模块
查看我们的 测试 部分,以获取更详细的信息,包括 测试模块文档、添加 集成测试 等说明。
注意
如果为 Ansible 做出贡献,每个新的模块和插件都应该有集成测试,即使这些测试不能在 Ansible CI 基础设施上运行。在这种情况下,应在 别名文件 中使用 unsupported
别名标记这些测试。
回馈 Ansible
如果你想通过添加新功能或修复错误来为 ansible-core
做出贡献,请创建 ansible/ansible 存储库的 fork,并以 devel
分支为起点,针对新的功能分支进行开发。当你有一个良好的工作代码更改时,你可以通过选择你的功能分支作为源,并选择 Ansible devel 分支作为目标,向 Ansible 存储库提交一个 pull 请求。
如果你想为 Ansible 集合贡献一个模块,请在打开 pull 请求之前,查看我们的 提交清单、编程技巧 和 维护 Python 2 和 Python 3 兼容性的策略,以及关于 测试 的信息。
社区指南 涵盖了如何打开 pull 请求以及接下来会发生什么。
沟通和开发支持
访问 Ansible 沟通指南 以获取有关如何加入对话的信息。
鸣谢
感谢 Thomas Stringer (@trstringer) 为本主题贡献了原始材料。