首页 > 系统相关 >使用shell,python,go来实现ansible的自定义模块

使用shell,python,go来实现ansible的自定义模块

时间:2023-04-12 23:55:10浏览次数:44  
标签:shell 自定义 err python module json 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\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来看看会发生什么?

总结大概经历了这么几个步骤:

  1. 在被控端创建临时目录,用于存放自定义模块、以及传入的参数
  2. 将自定义模块拷贝到被控端
  3. 将传入的参数拷贝到被控端
  4. 给被控端的自定义模块和传入的参数设置可执行权限
  5. 以传惨的方式在被控端执行自定义模块
  6. 删除临时目录

看到这里你应该就大概明白了原来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
}

总结下如何自定义一个模块:

  1. 编写代码逻辑实现我们所需的功能
  2. 代码的执行结果必须输出一个json,其中字段需要包括:changed, failed, rc, msg等字段,当然不是必须的。
  3. 编写完成后,文件名就是我们的模块名称
  4. 使用模块时,设置的参数会以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

相关文章

  • 今日总结-python连接数据库的学习
          ......
  • Mybatis-Plus如何自定义SQL注入器?
    有关Mybatis-Plus常用功能之前有做过一篇总结:MyBatisPlus常用功能总结!(附项目示例)一、什么是SQL注入器我们在使用Mybatis-Plus时,dao层都会去继承BaseMapper接口,这样就可以用BaseMapper接口所有的方法,BaseMapper中每一个方法其实就是一个SQL注入器在Mybatis-Plus的核心(core......
  • 3-面试题(python)
    1、列表和字典的区别字典是{}表示的,列表是[]表示的;字典是无序的不能通过索引来取值,列表是有序的;字典是以键值对的形式存在的,列表相当于一个容器,里面可以放置任何的数据类型; 2、python中的数据类型string、number、tuple、list、dictionary、set;3、python怎么将一个对象转......
  • 【NLP开发】Python实现聊天机器人(OpenAI,开发指南笔记)
    1、开始使用1.1介绍OpenAIAPI几乎可以应用于任何涉及理解或生成自然语言或代码的任务。我们提供一系列具有不同功率水平的型号,适用于不同的任务,并能够微调您自己的定制模型。这些模型可用于从内容生成到语义搜索和分类的所有内容。提示和完成(Promptsandcompletions)compl......
  • 【视频】随机波动率SV模型原理和Python对标普SP500股票指数预测|数据分享|附代码数据
    全文链接:http://tecdat.cn/?p=22546最近我们被客户要求撰写关于随机波动率SV模型的研究报告,包括一些图形和统计输出。什么是随机波动率?随机波动率(SV)是指资产价格的波动率是变化的而不是恒定的。 “随机”一词意味着某些变量是随机确定的,无法精确预测。在金融建模的背景......
  • 2-面试题:python
    1、python对象的比较和拷贝?答:'=='操作符比较对象之间的值是否相等;'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址;比较操作符'is'的速度效率,通常优于'==';浅拷贝和深拷贝:浅拷贝,将原对象或原数组的引用直接赋值给新对象、新数组,新对象/......
  • c sharp与python通信
    最近在学unity,想在unity调用python。因此学习了使用udp来建立通信。python发送,csharp接收python代码importsocketimporttimesock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)serverAddressPort=("127.0.0.1",10086)#5052定义localhost与端口,当然可......
  • 摸鱼用python代码收集每天大瓜内容信息,并发送到自己的邮箱
    本篇文章内容主要为如何用代码,把你想要的内容,以邮件的形式发送出去内容可以自己完善,还可以设置一个定时发送,或者开机启动自动运行代码代理注册与使用注册账号并登录生成api将自己电脑加入白名单http://api.tianqiip.com/white/add?key=xxx&brand=2&sign=xxx&ip=输入自己电脑的ip地......
  • 面试题:python
    列表和元组的区别列表是动态的,长度可变,可以对元素进行增、删、改操作;列表存储空间略大于元组,性能略逊于元组;元组是静态的,长度大小固定,不可以对元素进行增、删、改操作;元组相对于列表更加轻量级,性能稍优;字典和集合字典是有序的数据结构,而集合是无序的,其内部的哈希表存储结构,......
  • python习题-筛法求素数
    【题目描述】用户输入整数n和m(1<n<m<1000),应用筛法求[n,m]范围内的所有素数。【基本思想】用筛法求素数的基本思想是:把从2到N的一组正整数从小到大按顺序排列。从中依次删除2的倍数、3的倍数、5的倍数,直到根号N的倍数为止,剩余的即为2~N之间的所有素数。【源代码程序】defsie......