參考官網:http://www.ansible.com.cn/docs/developing_modules.html#tutorial
閱讀 ansible 附帶的模塊(上面鏈接)是學習如何編寫模塊的好方法。但是請記住,ansible 源代碼樹中的某些模塊是內在的,因此請查看service或yum,不要太靠近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
在劇本中定義變量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 }
如下:
[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