1. 为什么会有这篇文章的介绍呢?
在ansible文档中有一篇介绍用户自定义模块的文章 链接地址如下Developing Modules,但是在使用测试的时候总是有异常,无法继续进行下面的操作,于是就看了下是如何进行测试模块的.
2. 自定义模块分为两种情况
1
2
3
4
5
|
1
> 不传参数的,如下
# ansible -i hosts hostgroup -m ping -k
2
> 传递参数的, 如下
# ansible -i hsots hostgroup -m shell -a 'uptime' -k
|
ansible的文档上也给了两个对应的自定义模块的示例
1
2
3
4
5
6
7
8
9
10
|
1
> 不传参数的
#!/usr/bin/python
import
datetime
import
json
date
=
str
(datetime.datetime.now())
print
json.dumps({
"time"
: date
})
|
2> 传递参数的
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
#!/usr/bin/python
# import some python modules that we'll use. These are all
# available in Python's core
import
datetime
import
sys
import
json
import
os
import
shlex
# read the argument string from the arguments file
args_file
=
sys.argv[
1
]
args_data
=
file
(args_file).read()
# for this module, we're going to do key=value style arguments
# this is up to each module to decide what it wants, but all
# core modules besides 'command' and 'shell' take key=value
# so this is highly recommended
arguments
=
shlex.split(args_data)
for
arg
in
arguments:
# ignore any arguments without an equals in it
if
"="
in
arg:
(key, value)
=
arg.split(
"="
)
# if setting the time, the key 'time'
# will contain the value we want to set the time to
if
key
=
=
"time"
:
# now we'll affect the change. Many modules
# will strive to be 'idempotent', meaning they
# will only make changes when the desired state
# expressed to the module does not match
# the current state. Look at 'service'
# or 'yum' in the main git tree for an example
# of how that might look.
rc
=
os.system(
"date -s \"%s\""
%
value)
# always handle all possible errors
#
# when returning a failure, include 'failed'
# in the return data, and explain the failure
# in 'msg'. Both of these conventions are
# required however additional keys and values
# can be added.
if
rc !
=
0
:
print
json.dumps({
"failed"
:
True
,
"msg"
:
"failed setting the time"
})
sys.exit(
1
)
# when things do not fail, we do not
# have any restrictions on what kinds of
# data are returned, but it's always a
# good idea to include whether or not
# a change was made, as that will allow
# notifiers to be used in playbooks.
date
=
str
(datetime.datetime.now())
print
json.dumps({
"time"
: date,
"changed"
:
True
})
sys.exit(
0
)
# if no parameters are sent, the module may or
# may not error out, this one will just
# return the time
date
=
str
(datetime.datetime.now())
print
json.dumps({
"time"
: date
})
|
不论是带参数的还是不带参数的,模块写完之后该如何测试你写的模块是否正确呢?
ansible的文档上给了一种检测模块的方式:
Testing Modules
There’s a useful test script in the source checkout for ansible
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 下载测试自定义模块的脚本
1.
克隆ansible源码到本地
# git clone git://github.com/ansible/ansible.git --recursive
2.
source脚本中设定的环境变量到当前会话
# source ansible/hacking/env-setup
3.
赋予脚本执行权限
# chmod +x ansible/hacking/test-module
由于第一步在克隆的时候操作就失败了 索性直接将源码全部clone到本地 操作如下
# git clone https://github.com/ansible/ansible.git
|
3. 测试模块
1> 自定义模块不带参数传递 执行方式
比如你的脚本名字为timetest.py,那么执行命令如下所示
# ansible/hacking/test-module -m ./timetest.py
1
2
3
4
5
6
7
8
9
10
11
12
|
*
including generated source,
if
any
, saving to:
/
root
/
.ansible_module_generated
*
this may offset
any
line numbers
in
tracebacks
/
debuggers!
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
RAW OUTPUT
{
"time"
:
"2016-04-03 02:09:41.516592"
}
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
PARSED OUTPUT
{
"time"
:
"2016-04-03 02:09:41.516592"
}
|
2> 自定义模块带参数传递 执行方式
比如你的脚本名字为timetest.py,传递的参数为time="March 14 22:10",那么执行命令如下所示
# ansible/hacking/test-module -m ./timetest.py -a "time=\"March 14 12:23\""
带参数的这个地方执行失败 报错如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[root@ManagerAnsible sourceCode_tmp]
# ansible/hacking/test-module -m ../modules/timetest_params.py -a "time=\"March 14 12:23\""
*
including generated source,
if
any
, saving to:
/
root
/
.ansible_module_generated
*
this may offset
any
line numbers
in
tracebacks
/
debuggers!
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
RAW OUTPUT
Mon Mar
14
12
:
23
:
00
UTC
2016
{
"changed"
: true,
"time"
:
"2016-03-14 12:23:00.000262"
}
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
INVALID OUTPUT
FORMAT
Mon Mar
14
12
:
23
:
00
UTC
2016
{
"changed"
: true,
"time"
:
"2016-03-14 12:23:00.000262"
}
Traceback (most recent call last):
File
"ansible/hacking/test-module"
, line
167
,
in
runtest
results
=
json.loads(out)
File
"/usr/local/python27/lib/python2.7/json/__init__.py"
, line
339
,
in
loads
return
_default_decoder.decode(s)
File
"/usr/local/python27/lib/python2.7/json/decoder.py"
, line
364
,
in
decode
obj, end
=
self
.raw_decode(s, idx
=
_w(s,
0
).end())
File
"/usr/local/python27/lib/python2.7/json/decoder.py"
, line
382
,
in
raw_decode
raise
ValueError(
"No JSON object could be decoded"
)
ValueError: No JSON
object
could be decoded
|
从上面的报错可以追踪到ansible/hacking/test-module脚本的167行在json.loads对象的时候失败.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
try
:
print
(
"***********************************"
)
print
(
"RAW OUTPUT"
)
print
(out)
print
(err)
results
=
json.loads(out)
# 第167行
except
:
print
(
"***********************************"
)
print
(
"INVALID OUTPUT FORMAT"
)
print
(out)
traceback.print_exc()
sys.exit(
1
)
print
(
"***********************************"
)
print
(
"PARSED OUTPUT"
)
print
(jsonify(results,
format
=
True
))
|
至于为什么会出现这个问题,在文章的后面会有解决办法......
首先看timetest.py文件(注释比较多 代码量其实就几行)
正文 前两行没怎么看懂
args_file = sys.argv[1]
args_data = file(args_file).read()
1
2
3
4
5
|
# 接受一个参数
args_file
=
sys.argv[
1
]
# 打开这个参数 file <<>> open
args_data
=
file
(args_file).read()
/
/
开始纳闷了开始纳闷了开始纳闷了
|
于是又对这个文件ansible/hacking/test-module进行追踪
我对test-module添加了中文注释 有兴趣的朋友可以参考下 已经上传文章末尾到附件中.
解决了两个问题:
问题1:
ansible/hacking/test-module
有以下几个函数
parse # 接受命令行参数.
write_argsfile # 将命令行传递的参数写入到指定的文件中.
boilerplate_module # 将./timetest.py文件的内容全部写入到命令行-o默认指定的模块文件
runtest # 执行脚并打开参数本文件
总结下
boilerplate_module这个函数:将用户自定义的模块写入到~/.ansible_module_generated这个文件中
write_argsfile这个函数:将用户传递的参数写入到~/.ansible_test_module_arguments这个文件中
runtest这个函数:执行脚本和传递的参数~/.ansible_module_generated ~/.ansible_test_module_arguments
问题2:
修改文档中timetest.py代码
1
2
3
4
5
6
|
修改前
rc
=
os.system(
"date -s \"%s\""
%
value)
修改后
import
commands
rc, output
=
commands.getstatusoutput(
'date -s \"%s\"'
%
value)
|
其实有两处才让我想到是这里的原因:
原因1:
首先看timetest.py代码中 摘取一段
1
2
|
rc
=
os.system(
"date -s \"%s\""
%
value)
if
rc !
=
0
:
|
这个rc到底是获取os.system的命令执行结果还是获取os.system的返回值呢?
想必第二行的if语句你就弄明白.
原因2:
ansible/hacking/test-module文件
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
|
def
runtest( modfile, argspath):
"""Test run a module, piping it's output for reporting."""
os.system(
"chmod +x %s"
%
modfile)
invoke
=
"%s"
%
(modfile)
if
argspath
is
not
None
:
invoke
=
"%s %s"
%
(modfile, argspath)
cmd
=
subprocess.Popen(invoke, shell
=
True
, stdout
=
subprocess.PIPE, stderr
=
subprocess.PIPE)
(out, err)
=
cmd.communicate()
try
:
print
(
"***********************************"
)
print
(
"RAW OUTPUT"
)
print
(out)
print
(err)
results
=
json.loads(out)
except
:
print
(
"***********************************"
)
print
(
"INVALID OUTPUT FORMAT"
)
print
(out)
traceback.print_exc()
sys.exit(
1
)
print
(
"***********************************"
)
print
(
"PARSED OUTPUT"
)
print
(jsonify(results,
format
=
True
))
|
这个函数的正确返回结果肯定要是json格式的,而timetest.py文件有两处打印;第一处打印便是os.system的执行结果;第二处便是print json.dumps的结果,显然这是两行打印 无法进行json
那么我就举个列子来说明下吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 对timetest.py简写成如下格式
import
os, datetime, json
os.system(
'date -s %s'
%
3
)
date
=
str
(datetime.datetime.now())
print
json.dumps({
"time"
: date,
"changed"
:
True
})
# 那么test-module中的out就相当于上面执行结果的相加
"Mon Mar 14 03:00:00 UTC 2016"
+
"{"
changed
": true, "
time
": "
2016
-
03
-
14
03
:
00
:
00.013835
"}"
以上这种格式你是无法进行json.loads成json对象的 所以也就是报错的原因.
|
在文章的末尾就是如何使用用户自定义的模块呢,前面介绍了那么多都是如何测试模块,接下来就是用户如何正确的使用自定义完成的模块.
(1)通过ansible --help |grep module
1
2
3
4
|
-
m MODULE_NAME,
-
-
module
-
name
=
MODULE_NAME
module name to execute (default
=
command)
-
M MODULE_PATH,
-
-
module
-
path
=
MODULE_PATH
specify path(s) to module library (default
=
None
)
|
通过-M的方式来指定自定义模块的路径.
(2)通过ANSIBLE_LIBRARY 变量来指定
<<不够庆幸的是 前两种方式我Google好多文章也都没有解决,如果哪位朋友有解决的版本 也请告知>>
(3)我使用了最后一种比较笨的方式:
1
2
3
4
5
6
7
8
9
10
|
当前python版本为源码安装方式
2.7
版本
python的安装路径为
/
usr
/
local
/
python27
python模块包路径为
/
usr
/
local
/
python27
/
lib
/
python2.
7
/
site
-
packages
/
ansible
/
modules
其中有core(核心包)和extras(扩展包)两个目录
那么自定义模块 应该属于扩展包吧 于是做如下操作
# cd /usr/local/python27/lib/python2.7/site-packages/ansible/modules/extras
# mkdir zhengys
# cp ~/demo/ansible/modules/* ./
也就是把自定义的模块都放置与extras
/
zhengys目录下 就可以使用了
|
附件下载地址:http://pan.baidu.com/s/1kVQwgej