开发 Ansible 模块

模块是一段可重复使用的独立脚本,由 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 。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容