ansible自定义模块

 

参考官网://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、在过滤插件方法中对这个变量做过滤,然后返回结果(这里是定义空列表,然后将剧本变量列表遍历一次,筛选出指定条件的元素追加到新的列表中,方法返回新的列表这样剧本中就是使用过滤后的数据)

 

 

 

 参考地址:

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

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

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

Tags: