- 一、自定义模块运行原理
- 二、自定义模块实战
- 2.1 shell方式
- 2.2 python方式
- 2.3 golang方式
- 三、测试验证
- 3.1 shell方式验证
- 3.2 python方式验证
- 3.3 golang方式验证
ansible已经提供了非常多的模块,涵盖了系统、网络、数据库、容器、以及其他的方方面面的领域,几乎可以不用重复造轮子,只要你能想的到的,官方基本上都已经提供了,可以说极大的提高了我们的自动化效率,但是总会有些情况无法满足我们的需求,因此我们需要学会如何去编写一个自定义的模块
在下文的介绍中,会介绍下自定义模块的工作原理,分别以shell, python, golang等来实现自定义模块,通过这三种写法来实现修改主机的hosts文件的功能。
一、自定义模块运行原理
首先我们可以通过修改配置文件来设置自定义模块的位置,默认配置文件位置:/etc/ansible/ansible.cfg,例如如下配置:
[defaults]
# some basic default values...
library = /opt/workspace/ansible/library # 此目录可以随意设置
这里我通过修改ansible的配置文件,来配置我们存放自定义模块的目录,也就是说我们所编写的自定义模块,可以存放至此目录中,在使用自定义模块时就会从此目录进行拷贝,注意是拷贝。
下面我们在/opt/workspace/ansible/library这个目录中编写一个shell脚本,看看ansible在运行时做了什么?
#!/bin/bash
echo "hello world"
保存为文件:test_mod.sh,此时我们的模块名称就叫做:test_mod,接下来我们以ad-hoc的方式运行下这个模块,看看会发生什么?
# ansible localhost -m test_mod -a "name=tom age=18"
localhost | FAILED! => {
"changed": false,
"module_stderr": "",
"module_stdout": "hello world\n",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 0
}
可以看到,我们在通过ad-hoc执行模块时,传入了两个参数:name和age,这两个参数是我随意设置的,执行之后ansible输出了一段结果,看格式应该是一个json,其中module_stdout输出了我们脚本里的命令输出,msg却输出了一段内容:MODULE FAILURE\nSee stdout/stderr for the exact error,关于为什么会输出这个错误,这里先不说明,到后面大家就会明白。
接下来,我们开启debug来看看会发生什么?
总结大概经历了这么几个步骤:
- 在被控端创建临时目录,用于存放自定义模块、以及传入的参数
- 将自定义模块拷贝到被控端
- 将传入的参数拷贝到被控端
- 给被控端的自定义模块和传入的参数设置可执行权限
- 以传惨的方式在被控端执行自定义模块
- 删除临时目录
看到这里你应该就大概明白了原来ansible其实就把我们写的脚本拷贝到了目标机器上执行而已。
回到上面那个报错:MODULE FAILURE\nSee stdout/stderr for the exact error,这个报错实际上是因为我们没有输出内容到标准输出或标准错误,接下来我们改下test_mod.sh这个脚本,再来执行下试试
#!/bin/bash
_stdout() {
local changed=$1
local failed=$2
local rc=$3
local msg=$4
cat <<EOF
{
"changed": ${changed},
"failed": ${failed},
"rc": ${rc},
"msg": "${msg}"
}
EOF
}
_stdout true false 0 "sucess"
执行就不会有任何错误信息了
# ansible localhost -m test_mod -a "name=tom age=18"
localhost | CHANGED => {
"changed": true,
"msg": "sucess",
"rc": 0
}
总结下如何自定义一个模块:
- 编写代码逻辑实现我们所需的功能
- 代码的执行结果必须输出一个json,其中字段需要包括:changed, failed, rc, msg等字段,当然不是必须的。
- 编写完成后,文件名就是我们的模块名称
- 使用模块时,设置的参数会以json字符串的方式传给对应的模块,也就是说代码在去执行时,需要解析json内容,来获取所传入的参数内容
二、自定义模块实战
需求:修改本地hosts文件,来添加自定义的解析,要求传入两个参数,分别为host和domain表示要设置的ip地址和主机名称
2.1 shell方式
- 模块(文件)名称:set_hosts_by_shell
#!/bin/bash
# 导入变量文件
source $1
# 定义一个全局的输出格式
_stdout() {
local changed=$1
local failed=$2
local rc=$3
local msg=$4
cat << EOF
{
"changed": ${changed},
"failed": ${failed},
"rc": ${rc},
"msg": "${msg}"
}
EOF
}
# 检查传参,没有就报异常
_check_args() {
if [[ x"$host" == x ]];then
_stdout false true 1 "Missing args host"
exit 1
fi
if [[ x"$domain" == x ]];then
_stdout false true 1 "Missing args domain"
exit 1
fi
}
# 检查是否已经存在行,并给出返回码
_check_line() {
grep -Eo "$host\s+$domain" /etc/hosts >/dev/null
return $?
}
# 开始添加行
add_line() {
_check_args
_check_line
res=$?
# 为了幂等,已存在的行不再添加
if [[ $res -eq 1 ]];then
echo "$host $domain" >> /etc/hosts
_stdout true false 0 "Add $host $domain"
elif [[ $res -eq 0 ]];then
_stdout false false 0 "$host $domain existing!"
fi
}
# 执行函数
add_line
在脚本的开头我执行了一个source $1,这个是因为执行脚本传参的时候,在shell中会以类似kv的方式传入,也就是说内容类似于:
domain=www.baidu.com host=1.1.1.1 a=xxxx c=adsasda
所以当我执行source的时候,就会把这些参数注册到环境变量中。
2.2 python方式
- 模块名称:set_hosts_by_python
#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native
def add_lines(module, host, domain):
option_file = "/etc/hosts"
option_content = host + " " + domain
with open(option_file, "r+") as f:
contents = f.read()
if option_content in contents:
module.exit_json(changed=False, stdout="%s 已存在,无需添加" % option_content)
else:
f.write("%s\n" % option_content)
module.exit_json(changed=True, stdout="%s 已添加" % option_content)
return
def main():
module = AnsibleModule(
argument_spec=dict(
host=dict(type="str", required=True),
domain=dict(type="str", required=True),
)
)
host = module.params["host"]
domain = module.params["domain"]
try:
add_lines(module, host, domain)
except Exception as e:
module.fail_json(msg="Exception error: %s" % to_native(e))
if __name__ == "__main__":
main()
通过python来实现时,ansible提供了已经封装好的类AnsibleModule,通过实例化这个类,可以将所需的参数传入进去,同时输出执行结果时也提供了module.exit_json和module.fail_json的方法
2.3 golang方式
- 模块名称:set_hosts_by_go
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
type ModuleResult struct {
Changed bool `json:"changed"`
Fail bool `json:"fail"`
Msg string `json:"msg"`
RC int `json:"rc"`
}
// 定义要传入的参数
type AllArgs struct {
Domain string `json:"domain"`
Host string `json:"host"`
}
func addLines(host, domain string) (string, error) {
filename := "/etc/hosts"
content := host + " " + domain
data, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
isExist := false
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, content) {
isExist = true
return "已存在,无需添加", nil
}
}
if !isExist {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return "未知错误", err
}
defer f.Close()
if _, err := f.WriteString(content + "\n"); err != nil {
return "write error", err
}
}
return "write sucess", nil
}
func outPut(module ModuleResult, status int) {
module.RC = status
var out io.Writer
if status == 0 {
out = os.Stdout
} else {
out = os.Stderr
}
contents, _ := json.Marshal(module)
fmt.Fprint(out, string(contents))
os.Exit(status)
}
func parseArg(module ModuleResult, f string) (args AllArgs) {
fobj, err := os.Open(f)
if err != nil {
module.Changed = false
module.Fail = true
module.Msg = ""
outPut(module, 2)
}
defer fobj.Close()
content, err := ioutil.ReadAll(fobj)
if err != nil {
module.Changed = false
module.Fail = true
module.Msg = ""
outPut(module, 2)
}
err = json.Unmarshal(content, &args)
if err != nil {
module.Changed = false
module.Fail = true
module.Msg = ""
outPut(module, 2)
}
return
}
func main() {
module := ModuleResult{}
var argfile = os.Args[1]
var parsearg = parseArg(module, argfile)
host := parsearg.Host
domain := parsearg.Domain
res, err := addLines(host, domain)
if err != nil {
module.Changed = false
module.Fail = true
module.Msg = "添加失败 " + err.Error()
outPut(module, 2)
}
module.Changed = true
module.Fail = false
module.Msg = res
outPut(module, 0)
}
注意在使用go语言编写自定义模块时,不可以使用打印,例如使用fmt.Pringln来打印一些值,这种会造成执行中断,因为这会误导ansible,以为该模块已经执行完成,因为ansible会捕获标准输出。
三、测试验证
下面分别来验证下分别使用shell, python, golang来实现的自定义模块是否能完成我们的需求
3.1 shell方式验证
3.2 python方式验证
3.3 golang方式验证
❝
在使用golang编写模块时,需要先编译成二进制文件
欢迎关注公众号:feelwow
标签:shell,自定义,err,python,module,json,ansible,模块 From: https://www.cnblogs.com/dogfei/p/17311919.html