使用shell,python,go来实现ansible的自定义模块
一、自定义模块运行原理 二、自定义模块实战 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 ", "msg": "MODULE FAILURE See stdout/stderr for the exact error", "rc": 0 }
可以看到,我们在通过 ad-hoc 执行模块时,传入了两个参数:name 和age ,这两个参数是我随意设置的,执行之后ansible 输出了一段结果,看格式应该是一个json ,其中module_stdout 输出了我们脚本里的命令输出,msg 却输出了一段内容:MODULE FAILURE See stdout/stderr for the exact error ,关于为什么会输出这个错误,这里先不说明,到后面大家就会明白。
接下来,我们开启 debug 来看看会发生什么?
总结大概经历了这么几个步骤: 在被控端创建临时目录,用于存放自定义模块、以及传入的参数 将自定义模块拷贝到被控端 将传入的参数拷贝到被控端 给被控端的自定义模块和传入的参数设置可执行权限 以传惨的方式在被控端执行自定义模块 删除临时目录
看到这里你应该就大概明白了原来 ansible 其实就把我们写的脚本拷贝到了目标机器上执行而已。
回到上面那个报错: MODULE FAILURE See 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 < { "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 "$hosts+$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, pision, 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 " % 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 + " "); 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
科尔在这个联盟我们随时都可能要走人除了库里直播吧10月22日讯科尔在今日赛前接受了媒体群访,期间被问到了球队未来的阵容构建。我们就是专注于明天,我们会庆祝个人的成就,所以当这些家伙签下续约合同时,我们会庆祝一下,因为这对他
辛有志积极布局人才培育第三届百城千家敬老所万人爱心公益活动人才对于一个行业的未来发展来说至关重要,尤其是对直播电商行业这种新兴业态来说,无论人才数量还是质量,缺口都较大。如何规范化培养和储备人才是行业内外相关人员都在思考的问题。在行业头部
李宇春金钱也换不来健康娱评大赏近日,李宇春在一档电视节目中,自爆已经患上强直性脊柱炎,最严重的时候每天出行都需要坐轮椅。她在接受采访时说到世界上最贵的品牌汽车躺在我的车库里,但我必须坐在轮椅上,我的房子
孩子叛逆装病逃学玩手机成瘾可能是患了抑郁症北京大学第六医院儿童病房里,孩子们正在医护人员的带领下进行集体治疗。这里收治的孩子,最小的只有4岁,最大的不满18岁,其中以1216岁处于青春期的孩子居多,他们大都患有严重的精神障
油价10月25日,全国各地9295号汽油价格10月25日0时,国内成品油价格再次上调,具体为国内汽油价格每吨提高185元柴油价格每吨提高175元。折合成升,全国平均来看,92号汽油价格每升上调0。15元,95号汽油0号柴油同
高秀敏去世后,承诺帮她养女儿的赵本山,17年过去,做到了吗?2005年,46岁的演员高秀敏因突发心脏病离世。在她的葬礼上有两个男人哭得不能自己,一个是伴多年的亲密爱人何庆魁。另一个就是昔日铁三角的搭档赵本山。面对高秀敏的遗像,两个男人同时承
超级新人仅出场4分钟,北京男篮要放弃幻想,球队需要培养新人北京时间10月25日,CBA联赛继续进行。其中的一场比赛中,北京首钢男篮7571惊险战胜青岛男篮。整场比赛,两支球队都是处于均势之中。最终,北京首钢男篮还是依靠出色的防守能力限制住
内河运输开启纯电动时代!江苏这样打开船舶产业发展新空间全国首艘120标箱纯电动内河集装箱船江远百合号不久前在太仓投用以来,一直往返于太仓港和京杭运河苏州工业园区港间。作为航运新历史的书写者之一,船长何广科兴奋地表示,电动船零排放零污染
花开不止在春天在人们的眼中,春天是百花齐放争奇斗艳,是万物复苏,欣欣向荣。可它们不知道的是花开不止在春天,成功亦不止在舞台。人们常说,大学,是梦开始的地方。它奠定了我们闯荡人生的基石,架起了我们
UEFI的诞生与优势UEFI的诞生随着CPU及其他硬件设备的技术革新,CPU和操作系统已支持到64位,而BIOS却还停留在16位主板上更加丰富多样的扩展设备,也让BIOS愈加无能为力,使得硬件无法完成
金球奖得分公布本泽马549分领先优势非历史最大C罗0分北京时间本周二,2022年金球奖颁奖礼在法国巴黎进行,皇家马德里前锋本泽马毫无悬念地获得了金球奖。日前,法国足球公布了金球奖的得分情况,本泽马一共得到549分,比第二名马内高出35