用Bash脚本创建自定义Ansible模块
首先
只要了解一些简单的规则,用户可以自己创建并添加Ansible模块。
如果你对现有的shell脚本进行一些修改,按照那个规则来进行,你可以将其转化为Ansible模块。
另外,由于Ansible的playbook在语法允许的表达能力方面有时可能会出现难以编写的处理。通过将这些部分作为独立的模块提取出来,可能可以更轻松地实现编写。
在公式文档中主要介绍了Python模块的编写方式,但只要遵循规则,不一定非要使用Python,这里将说明如何将bash脚本转为Ansible模块。
Ansible模块的规则
只需遵循以下规则,即可将其作为 Ansible 模块。
-
- 将脚本文件放置在名为”library”的文件夹中,该文件夹位于具有playbook的目录中。
-
- 在脚本文件的第一行写入shebang行。
- 脚本文件将以”key=value”的形式输出到标准输出(可以有多个,用空格分隔)。
这里我会进一步解释一下。
安装模块的目录
需要创建一个名为library的目录,用于存放作为模块的脚本。
创建library目录的位置并不是随意的,必须在放置playbook的目录中创建。
如果不使用playbook,而是执行ansible命令而不是ansible-playbook命令,则应在当前目录中创建。
把作为模块的脚本文件放置在此library目录中,但脚本文件名不需要添加扩展名。虽然可以添加,但模块名仅仅与文件名相同,所以模块名也会成为带有扩展名的名字。
换句话说,只要在library目录中放置一个名为module1的脚本文件,模块名就会变成module1;如果放置一个名为module2.sh的脚本文件,模块名就会变成module2.sh。
不需要给所放置的脚本文件赋予执行权限。
请给出以下英文句子的中文表达:
– Can you give me some reference material?
– I need a reference for this project.
– I’ll provide a reference for you.
可以选择在环境变量ANSIBLE_LIBRARY上,或者在ansible命令或ansible-playbook命令的–module-path(-M)选项中指定模块的放置目录,而不是使用library目录。
可以在library目录中创建更多的子目录,将脚本文件放在其中也可以被识别,这样可以按照模块的类别进行目录分类。
参考: 对于将自制的Ansible模块库化时的目录结构。
脚本文件名不需要附加文件扩展名,唯一的例外是PowerShell脚本的扩展名为“.ps1”。将名为module3.ps1的脚本文件放置在library目录中,模块名称可以是module3或module3.ps1,无论哪个名称都可以调用。
她傻瓜行
需要将指定解释器的shebang行放置在用作模块的脚本文件的第一行。虽然说实话,脚本文件中通常已经有人提前写好了。对于bash脚本,可以使用#!/usr/bin/bash或者#!/usr/bin/env bash(适用于CentOS)。
输出到标准输出
如果不以key=value(如果有多个输出的话,用空格分隔)的格式输出到标准输出,则在模块执行时会发生错误。
换句话说,需要以以下格式输出到标准输出。
a=10 b=20 c=30
这个 key=value 的组合至少需要输出一个以上,否则会出错。
如果是 bash 脚本,最常见的方式是使用 echo 将输出发送到标准输出。
另外,由于似乎要忽略出现在最初的key=value之前的输出,所以只需要在脚本的结束部分输出key=value对就可以了。
如果在首次输出的key=value之后出现了不符合key=value格式的字符串,将会报错。
而且,似乎无论标准错误输出会输出什么内容,都会被忽略,特别是不会引发错误。
只需要一种选项:请给出以下内容的汉语本土化释义:
– 参考
key=value的分隔符可以是除了空格以外的换行符。
因此,可以多次执行输出key=value的echo命令,也是可以的。
也可以使用JSON格式而不是key=value的格式进行输出。
因此,你好世界。
最终,要创建并运行最简单的自制模块,只需要在安装了Ansible的计算机上执行以下步骤即可。
# mkdir library
# echo '#!/usr/bin/bash' > library/helloworld
# echo 'echo "hello=world"' >> library/helloworld
# ansible localhost -m helloworld
输出如下。
localhost | success >> {
"hello": "world"
}
我之前执行的echo只写了两行,但是还是给出library/helloworld的内容,以便参考。
#!/usr/bin/bash
echo "hello=world"
导致模块执行失败
按照规则创建的Ansible模块的执行总是不会失败。
即使从脚本中以非零退出,状态仍然是”ok”(成功),而不会是”failed”(失败)。
如果脚本在中途出现错误,想要将状态设为失败,应该怎么做呢?
首先,作为一种方法,我们可以利用标准输出不能输出key=value的特性来进行反向操作,从而导致错误。这种方法可以重复使用现有的脚本作为Ansible模块,并且如果原本在错误时已经输出了某些字符串到标准输出,那么修改的部分将会更少,因此这可能是一种有用的方法。
另外一种方法是在标准输出的字符串中包含rc=<0以外的数字>。从规范上来说,这种方法更加正确。
“0以外的数字”可以与exit的数字相同。
当rc=0时,状态将是ok。
在执行模块时将其更改为changed
如果在playbook中模块执行时状态为ok,则进一步检查是否发生了更改,最终将其报告给输出。
要从模块返回一个是否发生变化(changed)的信息,可以将包含”changed=1″的字符串输出到标准输出。实际上,不必一定是1,可以是0或者false。
模块示例
下面是一个名为m1的模块的例子,在这个模块中:
– 如果文件“/srv/mod”不存在,则创建该文件并返回“changed”。
– 如果文件“/srv/mod”已经存在,则不执行任何操作。
– 如果目录“/srv”本身不存在,则会报错。
#!/usr/bin/bash
FILENAME=/srv/mod
DIRNAME=`dirname $FILENAME`
if [ ! -d $DIRNAME ]; then
echo "rc=1 msg='dir not found'"
exit 1
elif [ -f $FILENAME ]; then
echo "rc=0 changed=0"
exit 0
fi
touch $FILENAME
echo "rc=0 changed=1"
指定选项
在Ansible中,几乎所有最初提供的模块都可以指定一些选项。
在playbook中,这些选项可以在”<模块名>:”后面进行指定;而在ansible命令中,则可以使用-a选项来指定。
当然,我们经常希望在自己编写的模块中同样能够指定选项来使用。
当在模块中指定选项时,它将被写入到与脚本文件不同的文件中,并传递给操作脚本文件的计算机。
指定的选项并不是作为命令行参数来执行脚本。
取而代之,将该选项所在文件的路径作为第一个参数传递。换句话说,在bash脚本中,可以这样做,将传递的选项存储在变量$OPTS中。
read OPTS < $1
另外,执行模块结束后,脚本和包含选项的文件将从操作的计算机上删除。如果希望保留这些文件以进行调试等目的,可以按以下方式执行。
ANSIBLE_KEEP_REMOTE_FILES=1 ansible -vvv (以下省略)
或者,
ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook -vvv (以下省略)
这些文件的路径可以通过-vvv选项显示出来。通常在$HOME/.ansible/tmp/ansible-tmp-<一系列表示时间的数字>-<一系列不明数字>的目录中创建并放置这些文件。
在Ansible中,提供的模块通常使用key=value格式来指定选项,这是因为Ansible为创建Python模块时提供了期望key=value格式的实用工具。但是,在使用基于Bash脚本制作的Ansible模块时,并不特别需要使用key=value格式。
模块示例
现在,在过去的m1模块中,我们已经将”/srv/mod”作为固定位置,并且在接下来的m2模块中,我们让它可以通过-f选项进行指定。
#!/usr/bin/bash
read OPTS < $1
GETOPT=`getopt -q -o f: -- $OPTS`
eval set -- "$GETOPT"
while :
do
case "$1" in
-f)
FILENAME=$2
shift 2
;;
--)
shift
break
;;
*)
shift
break
;;
esac
done
if [ -z $FILENAME ]; then
echo "rc=2 msg='option: -f <filename>'"
exit 2
fi
DIRNAME=`dirname $FILENAME`
if [ ! -d $DIRNAME ]; then
echo "rc=1 msg='dir not found'"
exit 1
elif [ -f $FILENAME ]; then
echo "rc=0 changed=0"
exit 0
fi
touch $FILENAME
echo "rc=0 changed=1"
处理选项时使用了getopt。
如果将”\$@”输入getopt中,则需要用双引号括起来作为”\$@”,但是需要注意在这里$OPTS不能用双引号括起来。
这个模块可以按以下方式执行,作为一个示例。
# ansible localhost -m m2 -a '-f /srv/mod'
最后
关于移植现有的bash脚本。
通过上面的示例,即使是现有的bash脚本,只要处理选项并输出到标准输出的部分进行一些调整,就可以将其移植为Ansible模块。
处理选项的部分看起来并不需要太大的修正。
然而,在一些较大的bash脚本中,可能会稍微麻烦地在所有带有exit的部分(至少是正常结束的部分)添加输出到标准输出的处理。尽管我没有尝试过,但使用trap可能会更简便。
此外,在移植一个调用另一个bash脚本的bash脚本时,可能需要采取一些措施,例如预先将另一个bash脚本复制到操作目标计算机中。