模块是一段可重复使用的独立脚本,由 Ansible 代表你在本地或远程运行。它可以与本地机器、API 或远程系统交互,执行修改数据库密码、启动云实例等特定任务。每个模块都可由 Ansible API、ansible 或 ansible-playbook 调用。模块提供明确的接口,接受参数,并在退出前向标准输出打印 JSON 字符串,把信息返回给 Ansible。
如果 collections 中数以千计的 Ansible 模块仍不能满足需求,你可以自行编写模块。仅供本地使用时,编程语言和内部规则可自行决定。本文介绍如何使用 Python 创建 Ansible 模块。创建后,必须把模块放入本地适当目录,让 Ansible 能够找到并执行它。
本地添加模块的具体方法见在本地添加模块与插件。
如果是在 collection 中开发模块,请改为参阅相应文档。
准备模块开发环境
测试模块只需安装 ansible-core。模块可以使用任何语言编写,但下文主要假设使用 Python。要纳入 Ansible 本身的模块必须使用 Python 或 PowerShell。
使用 Python 或 PowerShell 编写自定义模块的一个优势,是能够使用 module_utils 公共代码,处理参数、日志和响应输出等繁杂工作。
创建独立模块
强烈建议使用 venv 或 virtualenv 进行 Python 开发。
创建独立模块的步骤如下:
1. 在工作目录中创建 library 目录。测试 play 应位于同一目录。
2. 创建模块文件:$ touch library/my_test.py,也可以使用喜欢的编辑器直接创建或打开。
3. 把下方内容粘贴到模块文件。示例包含规定的 Ansible 格式与文档、声明模块选项的简单参数规范,以及示例代码。
4. 修改和扩展代码,实现所需功能。关于编写清晰简洁的模块代码,可参阅编程建议及 Python 3 兼容性。
在 collection 中创建模块
在现有的 my_namespace.my_collection collection 中创建模块:
1. 创建文件:$ touch <PATH_TO_COLLECTION>/ansible_collections/my_namespace/my_collection/plugins/modules/my_test.py,也可以直接在编辑器中创建。
2. 把下方内容粘贴到模块文件。它包含规定的格式与文档、声明模块选项的参数规范和示例代码。
3. 按需求修改和扩展代码。相关建议见编程建议和 Python 3 兼容性。
#!/usr/bin/python
# Copyright: (c) 2018, Terry Jones <terry.jones@example.org>
# 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()
创建 info 或 facts 模块
Ansible 使用 facts 模块收集目标机器的信息,使用 info 模块收集其他对象或文件的信息。如果你想在现有模块中加入 state: info 或 state: list,通常意味着应该新建专用的 _facts 或 _info 模块。
从 Ansible 2.8 开始,信息模块分为 *_info 和 *_facts 两类。
模块名为 <something>_facts 时,其主要目的应当是返回 ansible_facts。不返回这些事实的模块不要使用 _facts 后缀。ansible_facts 只用于特定主机的信息,例如网络接口及配置、操作系统、已安装程序。
查询或返回一般信息、而非 ansible_facts 的模块,应使用 _info 后缀。一般信息不专属于主机,例如在线或云服务的信息(同一主机可访问同一服务的不同账户)、机器能够访问的虚拟机和容器的信息,或某个文件、程序的信息。
Info 和 facts 模块与普通模块基本相同,但有以下要求:
1. **必须**命名为 <something>_info 或 <something>_facts,其中 <something> 使用单数。
2. *_info 模块**必须**以结果字典形式返回信息,供其他模块访问。
3. *_facts 模块**必须**通过结果字典的 ansible_facts 字段返回信息,供其他模块访问。
4. **必须**支持 check_mode。
5. **不得**修改系统。
6. **必须**为返回字段和示例编写文档。
可按下例把事实放入结果的 ansible_facts 字段:
module.exit_json(changed=False, ansible_facts=dict(my_new_fact=value_of_fact))
其余步骤与创建普通模块相同。
核验模块代码
把上述代码修改为所需功能后,即可尝试运行模块。核验时遇到错误,可参阅调试建议。
在本地核验
最简单的方法是使用 ansible 临时命令:
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
对于前文所述在 my_namespace.my_collection 中开发的模块:
$ ansible localhost -m my_namespace.my_collection.my_test -a 'name=hello new=true' --playbook-dir=$PWD
– 如果使用 pdb、print() 或其他本地调试方式加快迭代,可创建参数文件,直接运行模块而不经过 Ansible。参数文件是向模块传入参数的基本 JSON 配置文件。把它命名为 /tmp/args.json,加入以下内容:
{
"ANSIBLE_MODULE_ARGS": {
"name": "hello",
"new": true
}
}
– 随后可在本地直接测试模块。这会跳过打包步骤,直接使用 module_utils 文件。
$ python library/my_test.py /tmp/args.json
可能还需把 collection 路径加入 Python 路径,让 Python 在指定位置寻找额外的 module_utils 代码。可以像下面这样运行:
$ export PYTHONPATH=PATH_TO_COLLECTIONS:$PYTHONPATH
$ python -m ansible_collections.my_namespace.my_collection.plugins.modules.my_test /tmp/args.json
返回结果应类似下面的输出:
{"changed": true, "state": {"original_message": "hello", "new_message": "goodbye"}, "invocation": {"module_args": {"name": "hello", "new": true}}}
在 playbook 中核验
只要 library 目录与 play 所在目录相同,就可把模块放入 playbook,运行完整测试:
– 在任意目录创建 playbook:$ touch testmod.yml。
– 在新 playbook 文件中加入:
- 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 }}'
– 运行 playbook 并分析输出:$ ansible-playbook ./testmod.yml。
测试新模块
更详细的信息见测试章节,包括模块文档测试、添加集成测试等。
**注意**
向 Ansible 贡献模块时,每个新模块和插件都应有集成测试,即使测试无法在 Ansible CI 基础设施上运行。此时应在 aliases 文件中使用 unsupported 别名标记测试。
向 Ansible 贡献代码
如果希望为 ansible-core 添加功能或修复错误,应 fork ansible/ansible 仓库,以 devel 分支为起点建立功能分支并开发。代码修改可正常工作后,即可向 Ansible 仓库提交 pull request,以功能分支为源、Ansible 的 devel 分支为目标。
向 Ansible collection 贡献模块时,提交 pull request 前,请检查提交清单、编程建议、维护 Python 2 与 Python 3 兼容性的策略,以及测试相关资料。
社区指南介绍如何提交 pull request,以及之后的流程。
沟通与开发支持
加入讨论的方法见 Ansible 沟通指南。
致谢
感谢 Thomas Stringer(@trstringer)为本节贡献原始材料。
—
原文:Developing modules。作者:Ansible 文档贡献者;原始材料:Thomas Stringer。文档许可参阅官方文档门户的 CC BY-SA 4.0 声明;本文为中文翻译,沿用该许可。示例代码保留原始版权声明及 GNU GPL v3.0+ 许可;许可全文见 https://www.gnu.org/licenses/gpl-3.0.txt 。











暂无评论内容