死磕ansible系列--Ansible模块开发-自定义模块

第一步创建ansible自定义模块路径

1
2
cd /data/db/playbooks/
mkdir -p library

vim ansible.cfg 增加如下内容:

1
2
[defaults]
library = ./library

下面我们开始第一个模块开发

创建第一个模块

vim library/info.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/11/12 12:00 PM
# @Author : biglittle
# @Contact : biglittleant@hotmail.com
# @Site :
# @File : info.py
# @Software: PyCharm
# @Desc : python file
# @license : Copyright(C), Your Company

from ansible.module_utils.basic import *

# 实例化一个module,因为不需要参数所以argument_spec初始化参数为空字典。
module = AnsibleModule(
argument_spec = dict(),
)
output="hello word!"

result = dict(module='myinfo',stdout=output,changed=False,rc=0)

module.exit_json(**result)

1
2
3
4
5
6
7
8
9
10
ansible -i inventory/devlop linux-node1 -m myinfo
linux-node1 | SUCCESS => {
"changed": false,
"module": "myinfo",
"rc": 0,
"stdout": "hello word!",
"stdout_lines": [
"hello word!"
]
}

创建一个带参数的脚本

vim library/myinfo_args.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/11/12 12:00 PM
# @Author : biglittle
# @Contact : biglittleant@hotmail.com
# @Site :
# @File : myinfo_args.py
# @Software: PyCharm
# @Desc : python file
# @license : Copyright(C), Your Company

from ansible.module_utils.basic import *

module = AnsibleModule(
argument_spec = dict(
msg=dict(required=True),
),
)
msg = module.params['msg']

result = dict(module='myinfo_args',stdout=msg,changed=False,rc=0)

module.exit_json(**result)

执行验证

1
2
3
4
5
6
7
8
9
10
ansible -i inventory/devlop linux-node1 -m myinfo_args -a "msg='new word'"
linux-node1 | SUCCESS => {
"changed": false,
"module": "myinfo_args",
"rc": 0,
"stdout": "new word",
"stdout_lines": [
"new word"
]
}

来个不带参数的的执行

1
2
3
4
5
ansible -i inventory/devlop linux-node1 -m myinfo_args
linux-node1 | FAILED! => {
"changed": false,
"msg": "missing required arguments: msg"
}

自己实现一个shell模块

vim library/myshell.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020年01月10日16:39:09
# @Author : biglittle
# @Contact : biglittleant@hotmail.com
# @Site :
# @File : myshell.py
# @Software: PyCharm
# @Desc : python file
# @license : Copyright(C), Your Company
from ansible.module_utils.basic import AnsibleModule
import commands


def main():
"""
run shell
"""
changed = False
module = AnsibleModule(
argument_spec = dict(
cmd = dict(type='str', required=True),
),
)
cmd = module.params['cmd']

code,output = commands.getstatusoutput(cmd)
if code == 0:
# 按照ansible 的返回格式定义返回内容,stdout为标准输出,changed代表系统有没有东西被变更,rc=0代表执行成功
result = dict(stdout=output,changed=changed,rc=0)
# 使用ansible规则的module实例下的exit_json返回正常内容
module.exit_json(**result)
else:
# 当调用失败返回错误信息的时候,数据字典只要传递msg信息就可了,然后调用module实例的fail_json方法给返回
result = dict(msg=output,rc=code)
module.fail_json(**result)


if __name__ == '__main__':

main()

执行一个正确的命令

1
2
3
4
5
6
7
8
9
ansible -i inventory/devlop linux-node1 -m myshell -a cmd='pwd'
linux-node1 | SUCCESS => {
"changed": false,
"rc": 0,
"stdout": "/home/niu",
"stdout_lines": [
"/home/niu"
]
}
1
2
3
4
5
6
ansible -i inventory/devlop linux-node1 -m myshell -a cmd='pws'
linux-node1 | FAILED! => {
"changed": false,
"msg": "sh: pws: command not found",
"rc": 32512
}

如果不定义: result = dict(msg=output,rc=code) ansible 会有一个默认的返回,输出类似下面的情况。

1
2
3
4
5
6
7
8
ansible -i inventory/devlop linux-node1 -m myshell -a cmd='pws'
linux-node1 | FAILED! => {
"changed": false,
"module_stderr": "Shared connection to linux-node1 closed.\r\n",
"module_stdout": "\r\nTraceback (most recent call last):\r\n File \"/home/niu/.ansible/tmp/ansible-tmp-1578648957.53-161256530271090/AnsiballZ_myshell.py\", line 113, in <module>\r\n _ansiballz_main()\r\n File \"/home/niu/.ansible/tmp/ansible-tmp-1578648957.53-161256530271090/AnsiballZ_myshell.py\", line 105, in _ansiballz_main\r\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n File \"/home/niu/.ansible/tmp/ansible-tmp-1578648957.53-161256530271090/AnsiballZ_myshell.py\", line 48, in invoke_module\r\n imp.load_module('__main__', mod, module, MOD_DESC)\r\n File \"/tmp/ansible_myshell_payload_XylgIF/__main__.py\", line 40, in <module>\r\n File \"/tmp/ansible_myshell_payload_XylgIF/__main__.py\", line 33, in main\r\nUnboundLocalError: local variable 'result' referenced before assignment\r\n",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 1
}

参数解释

argument_spec 支持的参数

例子:

1
2
3
4
5
6
7
module = AnsibleModule(
argument_spec = dict{
name = dict(type='str', required=True),
cwd = dict(type='str', required=False),
shell = dict(type='bool', default=True),
}
)

官方的ping模块分析

模块路径:https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/ping.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from ansible.module_utils.basic import AnsibleModule


def main():
# 实例化了一个类
module = AnsibleModule(
argument_spec=dict(
data=dict(type='str', default='pong'),
),
supports_check_mode=True
)
# 判断参数是否为crash,如果是就抛出异常
if module.params['data'] == 'crash':
raise Exception("boom")
# 正常情况下,定义个字典。ping=data,data默认是pong
result = dict(
ping=module.params['data'],
)
# 返回结果
module.exit_json(**result)


if __name__ == '__main__':
main()
1
2
3
4
5
6
7
8
9
10
11
12
ansible -i inventory/devlop  linux-node1 -m ping
linux-node1 | SUCCESS => {
"changed": false,
"ping": "pong"
}

ansible -i inventory/devlop linux-node1 -m ping -a "data=abcd"
linux-node1 | SUCCESS => {
"changed": false,
"ping": "abcd"
}

ansible模块中的输出定制

changed: 平时我们使用别人的模块,会发现changed字段,有的时候是true,有的时候是false,其实changed并不代表什么,ansible里面一致认为的是对系统进行了更改的changed为true,未更改的为false,其实只是一个记录的值而已,要改变chnged的值,返回的字典里面只要changed=Fasle就好了result = dict(changed=False,stdout=ouput)

ansible模块中的退出状态处理

  • 正常退出:module.exit_jons

  • 错误退出:module.fail_json

错误退出比较不一样的是,你要传递的参数是msg: result = dict(msg=output,rc=code)

报错汇总

1
ERROR! this task 'myinfo_args' has extra params, which is only allowed in the following modules: shell, win_shell, include_vars, add_host, raw, include_role, meta, set_fact, include, import_tasks, script, import_role, include_tasks, group_by, command, win_command

命令执行错了 ansible -i inventory/devlop linux-node1 -m myinfo_args -a 'new word'

正确的命令:ansible -i inventory/devlop linux-node1 -m myinfo_args -a "msg='new word'"

参考文档

Ansible模块开发-自定义模块