返回总目录页

ansible自定义模块和扩展插件

 

参考官网:http://www.ansible.com.cn/docs/developing_modules.html#tutorial

 

阅读 ansible 附带的模块(上面链接)是学习如何编写模块的好方法。但是请记住,ansible 源代码树中的某些模块是内在的,因此请查看serviceyum,不要太靠近async_wrapper 之类的东西,否则您会变成石头。没有人直接执行 async_wrapper。

好的,让我们开始举例。我们将使用 Python。首先,将其保存为名为timetest.py的文件

#!/usr/bin/python

import datetime
import json

date = str(datetime.datetime.now())
print(json.dumps({
    "time" : date
}))
程序

 

 

自定义模块

创建模块目录

[root@mcw1 ~]$ mkdir /usr/share/my_modules   #这个目录并不存在,ansible配置中存在这个注释掉的路径

编写模块返回内容

[root@mcw1 ~]$ vim uptime

#!/usr/bin/python

import json
import os

up = os.popen('uptime').read()
dic = {"result":up}
print json.dumps(dic)

执行结果:

 

启动模块目录

[root@mcw1 ~]$ grep library /etc/ansible/ansible.cfg   #将配置中的这行内容注释,取消掉,不需要重启任何服务
library = /usr/share/my_modules/

测试模块使用情况

这里显示它使用的解释器路径了,这个解释器是python2的解释器,如果我写的是python3的脚本,并且不支持python2执行,我可能需要修改ansible默认使用的python解释器。有点问题,我脚本里写的是python2的解释器,我写成python3应该就是python3了吧

 

按照上面想法试了下,果然是的,我另一个主机是没有安装python3的,所以报错了。使用python3,貌似不会显示python的路径,跟之前python2有点区别

 

编写脚本:

 

 

程序如下:

[root@mcw1 ~]$ cat /usr/share/my_modules/mem
#!/usr/bin/python3
import subprocess,json
cmd="free -m |awk 'NR==2{print $2}'"
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
cmdres, err = p.communicate()
mcw=str(cmdres).lstrip("b'").rstrip("\\n'")
print(mcw)

dic = {
    "result":{
             "mem_total": mcw
          } ,
    "err": str(err)
}
print(json.dumps(dic))
#print(dic)

上面程序中,python2和python3去掉\n换行符存在区别。python2:rstrip("\n"),python3:rstrip("\\n")

无论是否dumps,打印结果都是一行。当将字典dumps后,结果好看很多

 

 当不使用dumps时,都是报错,但是有标准输出的还是会一行打印出来

 

 当我程序里将字典换成一行时,ansible输出内容还是有层次的显示出来。并且它把我们输出的字典所有键值对,相当于批量追加进ansible自己的输出字典中。同时它还会有自己的相关键值对,

 

 

总结:我们可以写python(shell应该也可以)脚本,将需要的信息构建字典,打印出来。然后将脚本放到自定义模块目录下,无需添加执行权限,就可以使用ansible调用自定义模块,将输出结果显示在ansible的打印结果字典中

 思考:上面总结打印的结果我怎么用?python中如何使用这个结果,ansible剧本中是否能使用这个模块,如何使用,这么用到它的打印结果?

 使用shell存在的问题

使用shell的方法还存在问题,有时间再看有办法解决这个问题

 

 

set_fact模块作用

设置变量,其它地方使用变量

- hosts: testB
  remote_user: root
  tasks:
  - set_fact:
      testvar: "testtest"
  - debug:
      msg: "{{testvar}}"

设置变量,其它地方使用变量

我们通过set_fact模块定义了一个名为testvar的变量,变量值为testtest,然后使用debug模块输出了这个变量:

设置变量接收其它变量的值,变量之间值的传递

如下:

- hosts: localhost
  remote_user: root
  vars:
    testvar1: test1_string
  tasks:
  - shell: "echo test2_string"
    register: shellreturn
  - set_fact:
      testsf1: "{{testvar1}}"
      testsf2: "{{shellreturn.stdout}}"
  - debug:
      msg: "{{testsf1}} {{testsf2}}"
      #var: shellreturn
剧本2

在剧本中定义变量testvar1,在剧本中可以引用这个变量。执行shell命令,将命令返回值注册为一个变量。set_fact模块设置两个变量,变量1让它等于前面设置的变量testvar1,变量2给它赋值shell命令的返回结果变量的标准输出。后面对这两个变量打印查看

剧本中想要查看信息,需要使用debug 模块(debug msg 打印)

set_fact:可以自定义变量,可以进行变量值的传递。可以用这个模块重新定义一个变量去接收其它变量的值,包括接收其它注册的变量值 

 

上例中,我们先定义了一个变量testvar1,又使用register将shell模块的返回值注册到了变量shellreturn中,

之后,使用set_fact模块将testvar1变量的值赋予了变量testsf1,将shellreturn变量中的stdout信息赋值给了testsf2变量,(可以将注释去掉查看变量shellreturn的值)

最后,使用debug模块输出了testsf1与testsf2的值:

vars关键字变量,set_fact设置变量和注册变量。vars的只在当前play生效,另外两个可以跨play

如上述示例所示,set_fact模块可以让我们在tasks中创建变量,也可以将一个变量的值赋值给另一个变量。

其实,通过set_fact模块创建的变量还有一个特殊性,通过set_fact创建的变量就像主机上的facts信息一样,可以在之后的play中被引用。

默认情况下,每个play执行之前都会执行一个名为”[Gathering Facts]”的默认任务,这个任务会收集对应主机的相关信息,我们可以称这些信息为facts信息,我们已经总结过怎样通过变量引用这些facts信息,此处不再赘述,而通过set_fact模块创建的变量可以在之后play中被引用,就好像主机的facts信息可以在play中引用一样,这样说可能还是不是特别容易理解,不如来看一个小例子,如下

- hosts: localhost
  remote_user: root
  vars:
    testvar1: tv1
  tasks:
  - set_fact:
      testvar2: tv2
  - debug:
      msg: "{{testvar1}} ----- {{testvar2}}"
- hosts: localhost
  remote_user: root
  tasks:
  - name: other play get testvar2
    debug:
      msg: "{{testvar2}}"
  - name: other play get testvar1
    debug:
      msg: "{{testvar1}}"
剧本

 

 变量1是vars关键字设置变量,在当前play生效,不能跨越play使用变量,但是变量2却可以跨越play使用变量,变量2是set_facts模块设置变量

 

 

可以发现,这两个变量在第一个play中都可以正常的输出。但是在第二个play中,testvar2可以被正常输出了,testvar1却不能被正常输出,会出现未定义testvar1的错误,因为在第一个play中针对testB主机进行操作时,testvar1是通过vars关键字创建的,而testvar2是通过set_fact创建的,所以testvar2就好像testB的facts信息一样,可以在第二个play中引用到,而创建testvar1变量的方式则不能达到这种效果,虽然testvar2就像facts信息一样能被之后的play引用,但是在facts信息中并不能找到testvar2,只是”效果上”与facts信息相同罢了。

通过注册变量实现跨play调用变量

- hosts: localhost
  remote_user: root
  vars:
    testvar3: tv3
  tasks:
  - shell: "echo tv4"
    register: testvar4
  - debug:
      msg: "{{testvar3}} -- {{testvar4.stdout}}"
- hosts: localhost
  remote_user: root
  tasks:
  - name: other play get testvar4
    debug:
      msg: "{{testvar4.stdout}}"
  - name: other play get testvar3
    debug:
      msg: "{{testvar3}}"
剧本

 

3是vars定义变量,4是注册变量,4是可以跨play的,3却不行 。是需要4还是3看情况

 

在第二个play中获取”testvar3″时会报错,而在第二个play中获取注册变量”testvar4″时则正常,但是,注册变量中的信息是模块的返回值,这并不是我们自定义的信息,所以,如果想要在tasks中给变量自定义信息,并且在之后的play操作同一个主机时能够使用到之前在tasks中定义的变量时,则可以使用set_facts定义对应的变量。

上述示例中,即使是跨play获取变量,也都是针对同一台主机。

自定义过滤插件

 

找到过滤插件所在的目录,当前没有任何过滤插件,新增一个插件deal_list_num.py

[root@mcw1 ~]$ ls /usr/share/ansible/plugins/filter
deal_list_num.py

 

 插件的好处在于编写YML文件时可以减少我们的工作量,而且结果易于展示,只要学习一些比较重要的比如Filter、Callbacks等即可。

    在普通情况下,我们主要是以{{somevars|filter}对somevars使用filter方法过滤,Ansible已经为我们提供了很多的过滤方法,比如找到列表中最大、最小数的max、min,把数据转换成JSON格式的fromjson等,但这还远远不够,我们还需要自定义一些过滤的方法来满足一些特殊的需求,比如查找列表中大于某个常数的所有数字等。以这个例子,展示如何编写。

    首先,到ansible.cfg中去掉#,打开filter_plugins的存放目录,filter_plugins=/usr/share/ansible/plugins/filter。

    编写deal_list_num.py文件,他主要提供过滤列表中的正数、负数和查询列表大于某一个给定数值这3个方法。/usr/share/ansible/filter/deal_list_num.py。

# !/usr/bin/env python
# encoding=utf-8
class FilterModule(object):
    def filters(self):    # 把使用的方法写在这个return中
        filter_map = {
            'positive': self.positive,
            'negative': self.negative,
            'no_less_than': self.no_less_than
        }
        return filter_map
    def positive(self, data):
        r_data = []
        for item in data:
            if item >= 0:
                r_data.append(item)
        return r_data
    def negative(self, data):
        r_data = []
        for item in data:
            if item <= 0:
                r_data.append(item)
        return r_data
    def no_less_than(self, data, num):    # 第1个变量为传入的数值,第2个为条件1
        r_data = []
        for item in data:
            if item >= num:
                r_data.append(item)
        return r_data
[root@mcw1 ~]$ cat deal_list_num.yml 
- hosts: localhost
  gather_facts: False
 
  tasks:
  - name: set fact
    set_fact:
      num_list: [-1,-2,5,3,1]
 
  - name: echo positive
    shell: echo {{num_list | positive}}
    register: print_positive
  - debug: msg="{{print_positive.stdout_lines[0]}}"
 
  - name: echo negative
    shell: echo {{num_list | negative}}
    register: print_negative
  - debug: msg="{{print_negative.stdout_lines[0]}}"
 
  - name: echo negative
    shell: echo {{num_list | no_less_than(4)}}
    register: print_negative
  - debug: msg="{{print_negative.stdout_lines[0]}}"
剧本

 

第一个是取列表中的正数,第二个是取列表中的负数。第三个是取列表中不小于4的数

 

 

 

变量引用和查看获取shell命令结果作为注册变量,该如何取到命令结果

 

过滤方法这里做个修改

 

 

 

 

 

 总结:

1、过滤插件定义类,类中定义方法,方法返回内容

2、将过滤插件放到ansible过滤插件目录下

3、剧本中{{变量}}的方式调用变量。然后变量后面|加过滤方法。这样就可以将变量传递进插件对应方法中,除了self之外的第一个位置参数就是这个剧本中的变量。

4、在过滤插件方法中对这个变量做过滤,然后返回结果(这里是定义空列表,然后将剧本变量列表遍历一次,筛选出指定条件的元素追加到新的列表中,方法返回新的列表这样剧本中就是使用过滤后的数据)

 扩展facts

本地添加fact文件以及查询添加的信息

过滤ansible_local,可以查看本地的fact信息,我们可以自定义生成字典类型的数据到默认的fact路径中。需要以.fact为文件后缀。它以ansible_local为键,在它下面。文件名mcw作为下一级的键,然后文件里面的字典作为它的值。可能不只是能存放字典,列表等或许也可以,下次再试

[root@mcw01 ~/mcw]$ ls /etc/ansible/
ansible.cfg  hosts  roles
[root@mcw01 ~/mcw]$ mkdir /etc/ansible/facts.d
[root@mcw01 ~/mcw]$ vim /etc/ansible/facts.d/mcw.fact
[root@mcw01 ~/mcw]$ cat /etc/ansible/facts.d/mcw.fact
{
    "name": "xiaoma",
    "list": ["three","one","two"],
    "Dict": {"A":"B"}
}
[root@mcw01 ~/mcw]$
[root@mcw01 ~/mcw]$ ansible 10.0.0.11 -m setup -a "filter=ansible_local"
10.0.0.11 | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "mcw": {
                "Dict": {
                    "A": "B"
                }, 
                "list": [
                    "three", 
                    "one", 
                    "two"
                ], 
                "name": "xiaoma"
            }
        }, 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false
}
[root@mcw01 ~/mcw]$ 

如下,我们用程序生成fact文件,然后可以让ansible使用这个信息了。一个文件就代表一组数据

[root@mcw01 ~/mcw]$ cat mcw.py 
import os,json,subprocess
p = subprocess.Popen("hostname", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
cmdres, err = p.communicate()
hostname=cmdres.strip()
p = subprocess.Popen("uptime", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
cmdres, err = p.communicate()
myuptime=cmdres.strip()
data={
    "myname": "xiaoma",
    "hostname": hostname,
    "myuptime": myuptime
}
with open('/etc/ansible/facts.d/my.fact',mode='wb') as f:
    f.write(json.dumps(data))
[root@mcw01 ~/mcw]$ ls /etc/ansible/facts.d/
mcw.fact
[root@mcw01 ~/mcw]$ python mcw.py 
[root@mcw01 ~/mcw]$ cat /etc/ansible/facts.d/my.fact 
{"myuptime": "00:29:25 up 19:01,  2 users,  load average: 0.20, 0.13, 0.14", "hostname": "mcw01", "myname": "xiaoma"}[root@mcw01 ~/mcw]$ 
[root@mcw01 ~/mcw]$ ansible 10.0.0.11 -m setup -a "filter=ansible_local"
10.0.0.11 | SUCCESS =>"ansible_facts": {
        "ansible_local": {
            "mcw": {
                "Dict": {
                    "A": "B"
                }, 
                "list": [
                    "three", 
                    "one", 
                    "two"
                ], 
                "name": "xiaoma"
            }, 
            "my": {
                "hostname": "mcw01", 
                "myname": "xiaoma", 
                "myuptime": "00:29:25 up 19:01,  2 users,  load average: 0.20, 0.13, 0.14"
            }
        }, 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false
}
[root@mcw01 ~/mcw]$ 

编写facts模块以及用模板渲染的的方式来测试变量

 facts模块info.py

[root@mcw01 ~/mcw]$ cat info.py 
#!/usr/bin/python
DOCUMENTATION="""
---
module: info
short_description: This modules is extend facts modules
description:
    - This modules is extend facts modules
version_added: "1.1"
options:
    enable:
    description:
        - enable extend facts
        required: true
        default: null
"""

EXAMPLES = '''
- info: enable=yes
'''

import json
import shlex
import sys
args_file = sys.argv[1]
args_data = file(args_file).read()
arguments=shlex.split(args_data)
for arg in arguments:
    if "="  in arg:
        (key,value)=arg.split("=")
        if key == "enable" and value == "yes":
            data={}
            data['key'] = 'value'
            data['list'] = ['one','two','three']
            data['dict'] = {'A':"a"}
            print(json.dumps({"ansible_facts":data},indent=4))
        else:
            print("info modules usage error")
    else:
        print("info modules need one parameter")
[root@mcw01 ~/mcw]$ 

使用这个模块

[root@mcw01 ~/mcw]$ ls
docker  hosts  info.py
[root@mcw01 ~/mcw]$ ansible   all  -m info -a 'enable=yes' -o
10.0.0.12 | FAILED! => {"msg": "The module info was not found in configured module paths"}
10.0.0.13 | FAILED! => {"msg": "The module info was not found in configured module paths"}
10.0.0.11 | FAILED! => {"msg": "The module info was not found in configured module paths"}
[root@mcw01 ~/mcw]$ ansible   all -M ./ -m info -a 'enable=yes' -o  #使用时需要指定模块路径,因为它不在默认的模块路径下,也可以把模块路径追加到ANSIBLE_LIBRARY这个环境变量下
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
10.0.0.12 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
10.0.0.13 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
10.0.0.11 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
[root@mcw01 ~/mcw]$ 

如下,当我们将当前模块所在的路径加入到ansible模块路径后,就不用在命令行-M指定模块路径了

[root@mcw01 ~/mcw]$ pwd
/root/mcw
[root@mcw01 ~/mcw]$ ls
docker  hosts  info.py
[root@mcw01 ~/mcw]$ vim /etc/profile
[root@mcw01 ~/mcw]$ tail -1 /etc/profile
export ANSIBLE_LIBRARY=${ANSIBLE_LIBRARY}:/root/mcw
[root@mcw01 ~/mcw]$ source /etc/profile
[root@mcw01 ~/mcw]$ ansible   all  -m info -a 'enable=yes' -o
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
10.0.0.13 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
10.0.0.12 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
10.0.0.11 | SUCCESS => {"ansible_facts": {"dict": {"A": "a"}, "discovered_interpreter_python": "/usr/bin/python", "key": "value", "list": ["one", "two", "three"]}, "changed": false}
[root@mcw01 ~/mcw]$ 

 callback插件

https://www.cnblogs.com/frankming/p/15937276.html

 日志回调插件的使用过程

查看回调插件目录,yum安装后这里没有回调插件
[root@mcw01 ~]$ ls /usr/share/ansible/plugins/
action/        cache/         cliconf/       doc_fragments/ httpapi/       lookup/        module_utils/  strategy/      test/
become/        callback/      connection/    filter/        inventory/     modules/       netconf/       terminal/      vars/
[root@mcw01 ~]$ ls /usr/share/ansible/plugins/callback/


查看我们的ansible日志目录,这里还没有日志目录
[root@mcw01 ~]$ ls /var/log/ansible/
ls: cannot access /var/log/ansible/: No such file or directory


到回调插件目录,创建如下回调插件,下面插件还有问题,下面有解决步骤
[root@mcw01 ~]$ cd /usr/share/ansible/plugins/callback
[root@mcw01 /usr/share/ansible/plugins/callback]$ ls
[root@mcw01 /usr/share/ansible/plugins/callback]$ vim log_plays.py
[root@mcw01 /usr/share/ansible/plugins/callback]$ cat log_plays.py 
import os,time,json
TIME_FORMAT="%b %d %Y %H:%M:%S"
MSG_FORMAT="%(now)s - %(category)s - %(date)s\n\n"

if not os.path.exists("/var/log/ansible/hosts"):
    os.makedirs("/var/log/ansible/hosts")
def log(host,catagory,data):   #category  n.    类别; (人或事物的)种类;这里传的是失败还是成功之类的级别
    if type(data) == dict:
        if 'verbose_override' in data:
            #avoid logging extraneous data from facts
            data='omitted'
        else:
            data=data.copy()
            invocation=data.pop('invocaton',None)
            data=json.dumps(data)
            if invocation is not None:
                data=json.dumps(invocation)+" => %s "%data
            path=os.path.join("/var/log/ansible/hosts",host)
            now=time.strftime(TIME_FORMAT,time.localtime())
            fd=open(path,"a")
            fd.write(MSG_FORMAT % dict(now=now,catagory=catagory,data=data))
            fd.close()
class CallbackModule(object):
    """
    logs playbook results.per host, in /var/log/ansible/hosts
    """
    def on_any(self,*args,**kwargs):
        pass
    def runner_on_failed(self,host,res,ignore_errors=False):
        log(host,"FAILED",res)
    def runner_on_ok(self,host,res):
        log(host,'OK',res)
    def runner_on_skipped(self,host,item=None):
        log(host,'SKIPPEND','...')
    def runner_on_unreachable(self,host,res):
        log(host,'UNREACHABLE',res)
    def runner_on_no_hosts(self):
        pass
    def runner_on_async_poll(self,host,res,jid,clock):
        pass
    def runner_on_async_ok(self,host,res,jid):
        pass
    def runner_on_async_failed(self,host,res,jid):
        log(host,'ASYNC_FAILED',res)
    def playbook_on_start(self):
        pass
    def playboook_on_notyfy(self,host,handler):
        pass
    def playbook_on_no_hosts_matched(self):
        pass
    def playbook_on_hosts_remaining(self):
        pass
    def playbook_on_task_start(self,name,is_conditional):
        pass
    def playbook_on_vars_prompt(self,varname,private=True,prompt=None,encrypt=None,confirm=False,salt_size=None,salt=None,default=None):
        pass
    def playbook_on_setup(self):
        pass
    def playbook_on_import_for_host(self,host,imported_file):
        log(host,'IMPORTED',imported_file)
    def playbook_on_not_import_for_host(self,host,missing_file):
        log(host,'NOTIMPORTED',missing_file)
    def playbook_no_play_start(self,name):
        pass
    def playbook_on_stats(self,stats):
        pass
[root@mcw01 /usr/share/ansible/plugins/callback]$ cd /root/mcw



查看日志目录,我们想要将日志写入/var/log/ansiible/hosts,但是目前是么有这个目录的
[root@mcw01 ~/mcw]$ ls /var/log/ansiible/hosts
ls: cannot access /var/log/ansiible/hosts: No such file or directory
[root@mcw01 ~/mcw]$ ls /var/log/ansiible/
ls: cannot access /var/log/ansiible/: No such file or directory


没有添加头文件,后面添加一下
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
[WARNING]: Skipping plugin (/usr/share/ansible/plugins/callback/log_plays.py) as it seems to be invalid: Non-ASCII character '\xe7' in file
/usr/share/ansible/plugins/callback/log_plays.py on line 7, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details
(log_plays.py, line 7)
10.0.0.12 | CHANGED | rc=0 | (stdout) mcw02
10.0.0.13 | CHANGED | rc=0 | (stdout) mcw03
10.0.0.11 | CHANGED | rc=0 | (stdout) mcw01
[root@mcw01 ~/mcw]$ head -1 /usr/share/ansible/plugins/callback/log_plays.py
# _*_ coding:utf-8 _*_
[root@mcw01 ~/mcw]$ 



执行一条ansible命令,发现目录有了 ,但是目录下并没有产生日志
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
10.0.0.12 | CHANGED | rc=0 | (stdout) mcw02
10.0.0.13 | CHANGED | rc=0 | (stdout) mcw03
10.0.0.11 | CHANGED | rc=0 | (stdout) mcw01
[root@mcw01 ~/mcw]$ ls /var/log/ansiible/hosts
ls: cannot access /var/log/ansiible/hosts: No such file or directory
[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts
[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts
[root@mcw01 ~/mcw]$ ls /var/log/ansible/
hosts
[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts/


这是因为默认ad-hoc命令回调插件是不记录日志的bin_ansible_callbacks = False,我们将配置文件改为true。
我们在回调插件中写一些打印命令,发现回调插件是执行了的,但是下面有报错,导致没有写入日志'module' object has no attribute 'dumps'。我们将这个注释掉,

[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts/
[root@mcw01 ~/mcw]$ vim /etc/ansible/ansible.cfg
[root@mcw01 ~/mcw]$ grep bin_ansible_callbacks /etc/ansible/ansible.cfg
#bin_ansible_callbacks = False
bin_ansible_callbacks = True
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
10.0.0.13 | CHANGED | rc=0 | (stdout) mcw03
[WARNING]: Failure using method (v2_runner_on_ok) in callback plugin (<ansible.plugins.callback.log_plays.CallbackModule object at 0x1387550>):
'module' object has no attribute 'dumps'
10.0.0.12 | CHANGED | rc=0 | (stdout) mcw02
10.0.0.11 | CHANGED | rc=0 | (stdout) mcw01
[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts/
[root@mcw01 ~/mcw]$ vim /usr/share/ansible/plugins/callback/log_plays.py



报错没有属性,'CallbackModule' object has no attribute 'set_options',我们让我们的回调类继承ansible的回调类,super一些init方法。
[root@mcw01 ~/mcw]$ vim /usr/share/ansible/plugins/callback/log_plays.py
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
[WARNING]: Skipping callback 'log_plays', unable to load due to: 'CallbackModule' object has no attribute 'set_options'
10.0.0.13 | CHANGED | rc=0 | (stdout) mcw03
10.0.0.12 | CHANGED | rc=0 | (stdout) mcw02
10.0.0.11 | CHANGED | rc=0 | (stdout) mcw01
[root@mcw01 ~/mcw]$ 


失败的使用方法,这是我们在程序里面将category写成了catagory写错了
[root@mcw01 ~/mcw]$ vim /usr/share/ansible/plugins/callback/log_plays.py
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
10.0.0.12 | CHANGED | rc=0 | (stdout) mcw02
{'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:51:38.092778', '_ansible_no_log': False, u'stdout': u'mcw02', u'cmd': u'hostname', u'start': u'2022-10-22 11:51:38.077437', u'delta': u'0:00:00.015341', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw02'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}
[WARNING]: Failure using method (v2_runner_on_ok) in callback plugin (<ansible.plugins.callback.log_plays.CallbackModule object at 0x29d0550>):
'category'




[root@mcw01 ~/mcw]$ 
[root@mcw01 ~/mcw]$ ansible all -m shell -a "hostname" -o
10.0.0.13 | CHANGED | rc=0 | (stdout) mcw03
{'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:22.189114', '_ansible_no_log': False, u'stdout': u'mcw03', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:22.176637', u'delta': u'0:00:00.012477', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw03'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}
10.0.0.12
.......


排查一些拼写错误后,如下,我们能看到有日志写入了,
[root@mcw01 ~/mcw]$ cat /var/log/ansible/hosts/10.0.0.11
Oct 22 2022 11:57:22 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:22.345112', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:22.319221', u'delta': u'0:00:00.025891', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 11:57:35 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:35.596558', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:35.578518', u'delta': u'0:00:00.018040', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

[root@mcw01 ~/mcw]$ ansible all -m shell -a "cat /root/mcw1" -o
10.0.0.13 | FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "cat /root/mcw1", "delta": "0:00:00.013326", "end": "2022-10-22 12:00:53.533299", "msg": "non-zero return code", "rc": 1, "start": "2022-10-22 12:00:53.519973", "stderr": "cat: /root/mcw1: No such file or directory", "stderr_lines": ["cat: /root/mcw1: No such file or directory"], "stdout": "", "stdout_lines": []}
{'stderr_lines': [u'cat: /root/mcw1: No such file or directory'], u'changed': True, u'end': u'2022-10-22 12:00:53.533299', '_ansible_no_log': False, u'stdout': u'', u'cmd': u'cat /root/mcw1', u'rc': 1, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'cat /root/mcw1', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'start': u'2022-10-22 12:00:53.519973', u'stderr': u'cat: /root/mcw1: No such file or directory', u'delta': u'0:00:00.013326', u'msg': u'non-zero return code', 'stdout_lines': [], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}
.......


还有错误日志等级别都能写入
[root@mcw01 ~/mcw]$ cat /var/log/ansible/hosts/10.0.0.11
Oct 22 2022 11:57:22 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:22.345112', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:22.319221', u'delta': u'0:00:00.025891', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 11:57:35 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:35.596558', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:35.578518', u'delta': u'0:00:00.018040', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 12:00:53 - FAILED - {'stderr_lines': [u'cat: /root/mcw1: No such file or directory'], u'changed': True, u'end': u'2022-10-22 12:00:53.837592', '_ansible_no_log': False, u'stdout': u'', u'cmd': u'cat /root/mcw1', u'rc': 1, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'cat /root/mcw1', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'start': u'2022-10-22 12:00:53.821874', u'stderr': u'cat: /root/mcw1: No such file or directory', u'delta': u'0:00:00.015718', u'msg': u'non-zero return code', 'stdout_lines': [], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

[root@mcw01 ~/mcw]$ vim /usr/share/ansible/plugins/callback/log_plays.py


我们执行剧本,看下剧本日志的写入。可以看到,剧本日志也能正常写入的
[root@mcw01 ~/mcw]$ ls
1.py  docker  hosts  info.j2  info.py  info.yaml
[root@mcw01 ~/mcw]$ cat info.yaml 
- hosts: all
  tasks:
    - name: test info facts module
      info: enable=yes
    - name: debug info facts
      template: src=info.j2 dest=/tmp/cpis
[root@mcw01 ~/mcw]$ ansible all -m info -a 'enable=yes'
........
[WARNING]: Module invocation had junk after the JSON data: info modules usage error info modules usage error info modules usage error info modules
usage error info modules usage error info modules usage error info modules usage error info modules usage error info modules usage error info
modules usage error info modules usage error info modules usage error info modules usage error info modules usage error
10.0.0.11 | SUCCESS => {
    "ansible_facts": {
        "dict": {
            "A": "a"
        }, 
        "discovered_interpreter_python": "/usr/bin/python", 
        "key": "value", 
        "list": [
            "one", 
            "two", 
            "three"
        ]
    }, 
    "changed": false
}
[root@mcw01 ~/mcw]$ cat /var/log/ansible/hosts/10.0.0.11
Oct 22 2022 11:57:22 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:22.345112', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:22.319221', u'delta': u'0:00:00.025891', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 11:57:35 - OK - {'stderr_lines': [], u'changed': True, u'end': u'2022-10-22 11:57:35.596558', '_ansible_no_log': False, u'stdout': u'mcw01', u'cmd': u'hostname', u'start': u'2022-10-22 11:57:35.578518', u'delta': u'0:00:00.018040', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'hostname', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [u'mcw01'], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 12:00:53 - FAILED - {'stderr_lines': [u'cat: /root/mcw1: No such file or directory'], u'changed': True, u'end': u'2022-10-22 12:00:53.837592', '_ansible_no_log': False, u'stdout': u'', u'cmd': u'cat /root/mcw1', u'rc': 1, u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'cat /root/mcw1', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'start': u'2022-10-22 12:00:53.821874', u'stderr': u'cat: /root/mcw1: No such file or directory', u'delta': u'0:00:00.015718', u'msg': u'non-zero return code', 'stdout_lines': [], 'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}}

Oct 22 2022 12:03:14 - OK - {'changed': False, u'ansible_facts': {u'dict': {u'A': u'a'}, u'discovered_interpreter_python': u'/usr/bin/python', u'list': [u'one', u'two', u'three'], u'key': u'value'}, '_ansible_no_log': False}  #剧本日志

[root@mcw01 ~/mcw]$ 

查看日志的目录结构,如下,可可以看到日志是按照主机ip去生成日志文件的。对主机相关的ansible执行情况会写入到对应的日志文件中
[root@mcw01 ~/mcw]$ ls /var/log/ansible/
hosts
[root@mcw01 ~/mcw]$ tree /var/log/ansible/
/var/log/ansible/
└── hosts
    ├── 10.0.0.11
    ├── 10.0.0.12
    └── 10.0.0.13

1 directory, 3 files
[root@mcw01 ~/mcw]$ 

日志回调插件以及如何使用

  • 修改配置文件
  • vim /etc/ansible/ansible.cfg
  • bin_ansible_callbacks = True
  • 将插件放入到默认的回调插件目录中
  • [root@mcw01 ~/mcw]$ ls /usr/share/ansible/plugins/callback/log_plays.py
  • /usr/share/ansible/plugins/callback/log_plays.py
  • 让自己的插件继承ansible的回调插件功能
  • from ansible.plugins.callback import CallbackBase
  • class CallbackModule(CallbackBase):
  •   def __init__(self):
  •     super(CallbackModule, self).__init__()
  • 定义日志函数,回调类的匹配到哪个,比如匹配到ok就会执行ok方法,可以把主机,res命令执行的结果传到我们写的函数中,这里是日志函数。然后在函数中对命令执行的结果进行操作。这里是有格式的写入到日志中。
默认ad-hoc命令回调插件是不记录日志的bin_ansible_callbacks = False,我们将配置文件改为true。
[root@mcw01 ~/mcw]$ ls /var/log/ansible/hosts/
[root@mcw01 ~/mcw]$ vim /etc/ansible/ansible.cfg
[root@mcw01 ~/mcw]$ grep bin_ansible_callbacks /etc/ansible/ansible.cfg
#bin_ansible_callbacks = False
bin_ansible_callbacks = True
[root@mcw01 ~/mcw]$ ls /usr/share/ansible/plugins/callback/log_plays.py
/usr/share/ansible/plugins/callback/log_plays.py
[root@mcw01 ~/mcw]$ cat /usr/share/ansible/plugins/callback/log_plays.py
# _*_ coding:utf-8 _*_
import os,time,json
from ansible.plugins.callback import CallbackBase
TIME_FORMAT="%b %d %Y %H:%M:%S"
MSG_FORMAT="%(now)s - %(category)s - %(data)s\n\n"

if not os.path.exists("/var/log/ansible/hosts"):
    os.makedirs("/var/log/ansible/hosts")
def log(host,category,data):   #category  n.    类别; (人或事物的)种类;这里传的是失败还是成功之类的级别
    if type(data) == dict:
        if 'verbose_override' in data:
            #avoid logging extraneous data from facts
            data='omitted'
        else:
            data=data.copy()
            invocation=data.pop('invocaton',None)
            #data=json.dumps(dict(data))
            if invocation is not None:
                data=json.dumps(dict(invocation))+" => %s "%data
            path=os.path.join("/var/log/ansible/hosts",host)
            now=time.strftime(TIME_FORMAT,time.localtime())
            fd=open(path,"a")
            fd.write(MSG_FORMAT % dict(now=now,category=category,data=data))
            fd.close()
class CallbackModule(CallbackBase):
    """
    logs playbook results.per host, in /var/log/ansible/hosts
    """
    def __init__(self):
  
        super(CallbackModule, self).__init__()
    def on_any(self,*args,**kwargs):
        pass
    def runner_on_failed(self,host,res,ignore_errors=False):
        log(host,"FAILED",res)
    def runner_on_ok(self,host,res):
        log(host,'OK',res)
    def runner_on_skipped(self,host,item=None):
        log(host,'SKIPPEND','...')
    def runner_on_unreachable(self,host,res):
        log(host,'UNREACHABLE',res)
    def runner_on_no_hosts(self):
        pass
    def runner_on_async_poll(self,host,res,jid,clock):
        pass
    def runner_on_async_ok(self,host,res,jid):
        pass
    def runner_on_async_failed(self,host,res,jid):
        log(host,'ASYNC_FAILED',res)
    def playbook_on_start(self):
        pass
    def playboook_on_notyfy(self,host,handler):
        pass
    def playbook_on_no_hosts_matched(self):
        pass
    def playbook_on_hosts_remaining(self):
        pass
    def playbook_on_task_start(self,name,is_conditional):
        pass
    def playbook_on_vars_prompt(self,varname,private=True,prompt=None,encrypt=None,confirm=False,salt_size=None,salt=None,default=None):
        pass
    def playbook_on_setup(self):
        pass
    def playbook_on_import_for_host(self,host,imported_file):
        log(host,'IMPORTED',imported_file)
    def playbook_on_not_import_for_host(self,host,missing_file):
        log(host,'NOTIMPORTED',missing_file)
    def playbook_no_play_start(self,name):
        pass                
    def playbook_on_stats(self,stats):
        pass
[root@mcw01 ~/mcw]$ 

 当我们写了回调插件之后,发现ansible执行过之后会生成 原文件名c文件。应该是生成字节码,方便下次使用吧

[root@mcw01 ~/mcw]$ cd /usr/share/ansible/plugins/callback/
[root@mcw01 /usr/share/ansible/plugins/callback]$ ls
log_plays.py  log_plays.pyc

如果找不到python包路径或者ansible库路径,直接环境变量指定

export ANSIBLE_LIBRARY=${ANSIBLE_LIBRARY}:/root/mcw
export PYTHONPATH=${PYTHONPATH}:/usr/local/lib64/python3.6/site-packages

回调插件编写之mysql案例

终于是成功了,不容易。下次有啥装不上的,可以先在pycharm终端上试试。这个python2的pip太坑了,装不上pymysql在linux和pycharm setting里面。还是我新建一个python项目,用python2的解释器,然后在pycharm终端上执行命令安装的,把多出来的包上传到linux上的python2的包目录下才成功连接数据库的。

 

 

 查看结果,执行我们的回调函数的打印信息

 

 查看数据库,有数据信息了

 

 程序如下:

和上面的日志回调程序差不多。只是将写日志到文件,变成保存数据到数据库

[root@mcw01 ~/mcw]$ cat /usr/share/ansible/plugins/callback/
log_plays.py   log_plays.pyc  status.py      status.pyc     
[root@mcw01 ~/mcw]$ cat /usr/share/ansible/plugins/callback/status.py
#!/usr/bin/python3
import os, time,datetime,pymysql
from ansible.plugins.callback import CallbackBase
import pymysql

TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
now=datetime.datetime.now()


def insert(host, res):
    conn = pymysql.connect(host='127.0.0.1', user='root', password="123456",
                           database='ansible',port=3306)
    cur = conn.cursor()
    sql='insert into status(hosts,result,date) values("%s","%s","%s" )'%(host,res,now.strftime(TIME_FORMAT))
    cur.execute(sql)
    conn.commit()
    conn.close()


class CallbackModule(CallbackBase):
    def __init__(self):
        super(CallbackModule, self).__init__()
    def on_any(self, *args, **kwargs):
        pass

    def runner_on_failed(self, host, res, ignore_errors=False):
        insert(host,res)

    def runner_on_ok(self, host, res):
        insert(host,res)

    def runner_on_unreachable(self, host, res):
        insert(host,res)

    def playbook_on_import_for_host(self, host, imported_file):
        pass

    def playbook_on_not_import_for_host(self, host, missing_file):
        pass

    def playbook_no_play_start(self, name):
        pass

    def playbook_on_stats(self, stats):
        hosts=stats.processed.keys()
        for i  in hosts:
            info=stats.summarize(i)
            if info['failures'] >0 or info['unreachable']>0:
                has_errors=True
            msg="Hosinfo: %s, ok: %d, failures: %d, unreachable: %d, changed: %d, skipped: %d"%(i,
                            info['ok'],info['failures'],info['unreachable'],info['changed'],info['skipped'])
            print(msg)
[root@mcw01 ~/mcw]$ 

 

 

 info=stats.summarize(i)  把ip放进去,得到的是结果字典

 lookup插件

使用过程

 ansible库,lookup表中的数据。我们需要使用lookup的方式让ansible调用到这些变量。这里有两个字段,三条表记录

 

 

 下面的程序带有打印的

[root@mcw01 ~/mcw]$ ls /tmp/lookup 
/tmp/lookup
[root@mcw01 ~/mcw]$ cat /tmp/lookup
ONE
[root@mcw01 ~/mcw]$ cat lookup.yaml 
- hosts: all
  vars:
    value: "{{ lookup('mysql',('127.0.0.1','ansible','lookup','one')) }}"
  tasks: 
    - name: test lookup
      template: src=lookup.j2 dest=/tmp/lookup
[root@mcw01 ~/mcw]$ cat lookup.j2 
{{ value }}
[root@mcw01 ~/mcw]$ 
[root@mcw01 ~/mcw]$ cat  /usr/share/ansible/plugins/lookup/mysql.py
#!/usr/bin/python

"""
Description: This lookup query value from mysql
Example Usgge:
{{ lookup('mysql',('192.168.1.117','Ansible','lookup','ansible')) }}
"""
from ansible import utils,errors
from ansible.plugins.lookup import LookupBase

HAVE_MYSQL=False
try:
    import pymysql
    HAVE_MYSQL=True
except ImportError:
    pass
class LookupModule(LookupBase):

    #def __init__(self,basedir=None,**kwargs):
    #super(LookupModule, self).__init__()
        #self.basedir = basedir

        #if HAVE_MYSQL == False:
         #   raise errors.AnsibleError("Can't LOOKUP(mysql): module pymysql is not installed")
    #print 222222
    def run(self,terms,inject=None,**kwargs):
        #terms = utils.listify_lookup_plugin_terms(terms,self.basedir,inject)
        ret = []
        print(terms) # ('terms:  ', [['127.0.0.1', 'ansible', 'lookup', 'one']])
        for term in terms:
        print(term)
            host,db,table,key=term[0],term[1],term[2],term[3]
            conn=pymysql.connect(host=host,user='root',passwd='123456',db=db,port=3306)
            cur=conn.cursor()
            sql='select value from %s where keyname = "%s"'%(table,key)
            cur.execute(sql)
            result=cur.fetchone()
            if result[0]:
                ret.append(result[0])
        return ret

[root@mcw01 ~/mcw]$ 

 

 

 

如下成功从数据库查到数据并渲染在模板上

如下,当我们给所有主机执行这个剧本时,主机2,主机3上都生成了这个文件,查询到数据库信息渲染在各自主机的这个文件中。主机2,3是没有pymysql这个模块的,也就是ansible主机渲染好之后发到其它主机的。主机1因为已经有了这个文件了,所以没有改变

 

 

 

 程序(从MySQL中查询数据的lookup插件)

其它人写的没有继承base模块,不知道咋实现的。反正我试了试是没成功

[root@mcw01 ~/mcw]$ cat /usr/share/ansible/plugins/lookup/mysql.py

#!/usr/bin/python

"""
Description: This lookup query value from mysql
Example Usgge:
{{ lookup('mysql',('192.168.1.117','Ansible','lookup','ansible')) }}
"""
from ansible import utils,errors
from ansible.plugins.lookup import LookupBase

HAVE_MYSQL=False
try:
    import pymysql
    HAVE_MYSQL=True
except ImportError:
    pass
class LookupModule(LookupBase):
    def run(self,terms,inject=None,**kwargs):
        #terms = utils.listify_lookup_plugin_terms(terms,self.basedir,inject)
        ret = []
        for term in terms:
            host,db,table,key=term[0],term[1],term[2],term[3]
            conn=pymysql.connect(host=host,user='root',passwd='123456',db=db,port=3306)
            cur=conn.cursor()
            sql='select value from %s where keyname = "%s"'%(table,key)
            cur.execute(sql)
            result=cur.fetchone()
            if result[0]:
                ret.append(result[0])
        return ret

解析程序

 

#!/usr/bin/python

"""
Description: This lookup query value from mysql
Example Usgge:
{{ lookup('mysql',('192.168.1.117','Ansible','lookup','ansible')) }}
"""
from ansible import utils,errors
from ansible.plugins.lookup import LookupBase

HAVE_MYSQL=False
try:
    import pymysql
    HAVE_MYSQL=True
except ImportError:
    pass
class LookupModule(LookupBase):  #需要继承插件基础。
    def run(self,terms,inject=None,**kwargs):  #这 应该是重写父的run方法。
        #terms = utils.listify_lookup_plugin_terms(terms,self.basedir,inject) #这个方法别人用的有,我这里没有,可能版本不同吧
print(terms) # [['127.0.0.1', 'ansible', 'lookup', 'one']] ret
= [] for term in terms: host,db,table,key=term[0],term[1],term[2],term[3] conn=pymysql.connect(host=host,user='root',passwd='123456',db=db,port=3306) cur=conn.cursor() sql='select value from %s where keyname = "%s"'%(table,key) cur.execute(sql) result=cur.fetchone() if result[0]: ret.append(result[0]) return ret

 

[root@mcw01 ~/mcw]$ cat lookup.yaml 
- hosts: all
  vars:
    value: "{{ lookup('mysql',('127.0.0.1','ansible','lookup','one')) }}" #这里的mysql,应该是指我们的插件名称是mysql,我们的脚本名就是mysql.py
  tasks:                                              #第二个参数是个元组,元组里面将主机,数据库,表名,指定字段keyname是one的表记录。这里填写后,插件里lookup
    - name: test lookup                      #继承了base插件,重写了run方法,run方法的terms会接收这个元组。我们可以在插件里通过这个元组数据,连接数据库,查询出字段是one的表记录的
      template: src=lookup.j2 dest=/tmp/lookup  #指定字段值,这里查的是 value字段。

我们将回调类run方法的返回数据打印一下,发现返回的是个列表。列表里面就是我们从数据库里查出来的数据。

 

 那么我们是怎么调用这个数据的呢

剧本中我们定义了一个变量,使用了这个lookup插件。run方法返回的数据列表,模板这里变量接收的是列表里面数据元素。剧本中用变量value接收到从数据库查出的数据后,就可以在剧本里或者剧本里使用的模板中,调用这个变量。从而实现剧本中使用从MySQL中查出来的指定数据

 

 系统中自带的lookup插件redis,可做参考去写(我们写相关插件,都可以去系统里面找,然后模仿着去写)

[root@mcw01 ~/mcw]$  find /  -name "lookup"
find: ‘/proc/28956’: No such file or directory
/tmp/lookup
/usr/lib/python2.7/site-packagesbak/ansible/plugins/lookup
/usr/lib/python2.7/site-packages/ansible/plugins/lookup
/usr/share/ansible/plugins/lookup
[root@mcw01 ~/mcw]$ ls /usr/lib/python2.7/site-packages/ansible/plugins/lookup|grep -i redis
redis.py
redis.pyc
redis.pyo
[root@mcw01 ~/mcw]$ cat /usr/lib/python2.7/site-packages/ansible/plugins/lookup/redis.py
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = """
    lookup: redis
    author:
      - Jan-Piet Mens (@jpmens) <jpmens(at)gmail.com>
      - Ansible Core
    version_added: "2.5"
    short_description: fetch data from Redis
    description:
      - This lookup returns a list of results from a Redis DB corresponding to a list of items given to it
    requirements:
      - redis (python library https://github.com/andymccurdy/redis-py/)
    options:
      _terms:
        description: list of keys to query
      host:
        description: location of Redis host
        default: '127.0.0.1'
        env:
          - name: ANSIBLE_REDIS_HOST
        ini:
          - section: lookup_redis
            key: host
      port:
        description: port on which Redis is listening on
        default: 6379
        type: int
        env:
          - name: ANSIBLE_REDIS_PORT
        ini:
          - section: lookup_redis
            key: port
      socket:
        description: path to socket on which to query Redis, this option overrides host and port options when set.
        type: path
        env:
          - name: ANSIBLE_REDIS_SOCKET
        ini:
          - section: lookup_redis
            key: socket
"""

EXAMPLES = """
- name: query redis for somekey (default or configured settings used)
  debug: msg="{{ lookup('redis', 'somekey') }}"

- name: query redis for list of keys and non-default host and port
  debug: msg="{{ lookup('redis', item, host='myredis.internal.com', port=2121) }}"
  loop: '{{list_of_redis_keys}}'

- name: use list directly
  debug: msg="{{ lookup('redis', 'key1', 'key2', 'key3') }}"

- name: use list directly with a socket
  debug: msg="{{ lookup('redis', 'key1', 'key2', socket='/var/tmp/redis.sock') }}"

"""

RETURN = """
_raw:
  description: value(s) stored in Redis
"""

import os

HAVE_REDIS = False
try:
    import redis
    HAVE_REDIS = True
except ImportError:
    pass

from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase


class LookupModule(LookupBase):

    def run(self, terms, variables, **kwargs):

        if not HAVE_REDIS:
            raise AnsibleError("Can't LOOKUP(redis_kv): module redis is not installed")

        # get options
        self.set_options(direct=kwargs)

        # setup connection
        host = self.get_option('host')
        port = self.get_option('port')
        socket = self.get_option('socket')
        if socket is None:
            conn = redis.Redis(host=host, port=port)
        else:
            conn = redis.Redis(unix_socket_path=socket)

        ret = []
        for term in terms:
            try:
                res = conn.get(term)
                if res is None:
                    res = ""
                ret.append(to_text(res))
            except Exception as e:
                # connection failed or key not found
                raise AnsibleError('Encountered exception while fetching {0}: {1}'.format(term, e))
        return ret
[root@mcw01 ~/mcw]$ 

 jinja2 filter组件

 jinja2 filter插件源码案例fileglob函数分析使用

我这里是这个路径。

[root@mcw01 ~/mcw]$ more /usr/lib/python2.7/site-packages/ansible/plugins/filter/core.py

函数根据指导的目录返回目录下匹配到的文件

 定义的过滤类,它这里就没有说继承哪个哪个的,只是默认的继承。

 

 我们再看这个文件函数。也就是过滤类,返回的是这个函数名称。估计某个地方,会循环这个字典,然后调用这些函数吧。至少核心模块这里是没有做函数调用的

 

 下面我们看看是如何使用这个过滤插件的。我们定义了剧本,剧本里面使用了模板,我们定义了模板里的变量调用。先设置了一个路径和通配的文件,然后调用变量交给过滤插件,结果是。过滤插件根据通配符去匹配到文件,然后将匹配到的文件放到列表中返回。我们通过这个插件可以模糊本地以及远端服务器文件。jinja2可能是不支持通配符等之类的,但是我们可以写插件,把通配方式写到模板中,然后使用过滤插件,过滤插件中我们可以调用shell命令通配或者正则可以进行模糊匹配。将结果返回。之前我们看的这个插件函数的返回就是一个列表,还用了三元运算。如果不使用fileglob插件的话,我们可以看到,模板中定义的变量是什么就返回什么,是不会进行通配符匹配的,通配符匹配是过滤插件做的事情。

[root@mcw01 ~/mcw]$ cat filter.yaml  
- hosts: all
  tasks:
    - name: test jinja2 filter
      template: src=filter.j2 dest=/tmp/filter
[root@mcw01 ~/mcw]$ cat filter.j2 
{% set path = '/root/*.yaml' %}
  {{ path }}
  {{ path | fileglob }}
[root@mcw01 ~/mcw]$ ansible-playbook filter.yaml -l 10.0.0.11

PLAY [all] *****************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************
ok: [10.0.0.11]

TASK [test jinja2 filter] **************************************************************************************************************************
changed: [10.0.0.11]

PLAY RECAP *****************************************************************************************************************************************
10.0.0.11                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Hosinfo: 10.0.0.11, ok: 2, failures: 0, unreachable: 0, changed: 1, skipped: 0
[root@mcw01 ~/mcw]$ cat /tmp/filter 
  /root/*.yaml
  ['/root/template.yaml', '/root/nginx.yaml', '/root/CentOS.yaml', '/root/site.yaml']
[root@mcw01 ~/mcw]$ ls /root/*.yaml
/root/CentOS.yaml  /root/nginx.yaml  /root/site.yaml  /root/template.yaml
[root@mcw01 ~/mcw]$ 

 自定义Jinja2 filter

#!/usr/bin/python
from jinja2.filters import environmentfilter
from ansible import errors
import time
def string(str,seperator=' '):
    return str.split(seperator)
def _time(times):
    return time.mktime(time.strptime(times,'%Y-%m-%d %H:%M:%S'))
class FilterModule(object):
    '''Ansible custom Filter'''
    def filters(self):
        return {
            'strPlug':string,
            'timePlug':_time
        }
自定义的filter.py

如下:

[root@mcw01 ~/mcw]$ vim filter.j2 
[root@mcw01 ~/mcw]$ vim filter.yaml 
[root@mcw01 ~/mcw]$ vim /usr/share/ansible/plugins/filter/filter.py
[root@mcw01 ~/mcw]$ ls /usr/share/ansible/plugins/filter/
filter.py
[root@mcw01 ~/mcw]$ cat /usr/share/ansible/plugins/filter/filter.py  #创建过滤函数。过滤类。
#!/usr/bin/python
from jinja2.filters import environmentfilter
from ansible import errors
import time
def string(str,seperator=' '):  这里的模板调用时,只传了分隔符就行了。貌似过滤函数,第一个参数就是将竖线前面的字符串接收过来。函数第一个参数在模板中不需要传递,
    return str.split(seperator)   #但是第二个参数是需要模板中传递的。如下模板的参考。如果过滤函数只有一个变量本身的传递,那么在模板中使用这个过滤方法时,方法不需要加括号,括号里面放
                  #参数的。只有函数的第二个参数开始,才需要模板中使用这个过滤方法时加括号,加这个参数。 def _time(times): #定义过滤函数
return time.mktime(time.strptime(times,'%Y-%m-%d %H:%M:%S')) class FilterModule(object): #定义过滤类 '''Ansible custom Filter''' def filters(self): #过滤类中,定义filters方法。返回一个字典 return { 'strPlug':string, #字典中键是我们在模板中竖线接过滤函数的名称,ansible会根据模板中使用的名称,找到这里,再找到它对应的函数名称,这字典就相当于字符串和函数名称的映射 'timePlug':_time #估计哪里做了调用这个方法,用字符串反射获取到函数,执行函数吧。执行函数后,就将函数返回的数据渲染在模板调用的地方。 } [root@mcw01 ~/mcw]$ cat filter.yaml - hosts: all tasks: - name: test jinja2 filter template: src=filter.j2 dest=/tmp/filter #剧本中使用模板,模板中使用过滤函数。模板中根据过滤函数去插件中匹配到对应函数对象, [root@mcw01 ~/mcw]$ cat filter.j2 #函数对象中接收参数,第一个就是模板中调用这个过滤函数的字符串。函数对象中对字符串处理,然后返回处理后的数据。 {% set String = 'www|machangwei|com' %} #模板中会将处理后的数据渲染在模板中 {{ String | strPlug('|') }} #传分隔符就好 {% set Time = '2022-10-23 12:48:01' %} {{ Time | timePlug }}

 执行结果如下:

 

 

 

 

 

 

 

 

 参考地址:

剧本编写和定义模块: https://blog.csdn.net/weixin_46108954/article/details/104990063

设置和注册变量:https://blog.csdn.net/weixin_45029822/article/details/105280206

自定义插件:过滤插件:https://blog.csdn.net/JackLiu16/article/details/82121044    

跟别人写的不一样,可以参考一下:https://blog.csdn.net/qq_25518029/article/details/120361753                                                                                                                                                                                                  

posted @ 2021-12-20 18:59  马昌伟  阅读(1776)  评论(0编辑  收藏  举报
博主链接地址:https://www.cnblogs.com/machangwei-8/