Ansible專欄文章之十六:成就感源於創造,自己動手寫Ansible模塊


回到:Ansible系列文章


各位讀者,請您:由於Ansible使用Jinja2模板,它的模板語法{% raw %} {{}} {% endraw %}和{% raw %} {%%} {% endraw %}和博客系統的模板使用的符號一樣,在渲染時會產生衝突,儘管我盡我努力地花了大量時間做了調整,但無法保證已經全部都調整。因此,如果各位閱讀時發現一些明顯的詭異的錯誤(比如像這樣的空的 行內代碼),請一定要回復我修正這些渲染錯誤。

16.成就感源於創造:自己動手寫Ansible模塊

從小,書上就告訴我們"自己動手,豐衣足食",但是在IT領域裏,這句話不完全對,其實自己不動手,也可以豐衣足食,因爲這個領域裏提倡的是"不要重複造輪子",別人已經把輪子造好了,直接拿來用就好,簡單又高效。但自己造輪子,總是有好處的,至少收穫了造輪子的過程,有些輪子,是前進道路上必造不可的。

閒話只扯一段。Ansible提供了大量已經造好的輪子,幾千個模塊(此刻是3387個)、很多個插件可以供用戶直接使用,基本上能解決絕大多數場景的需求。但是,再多的模塊也不夠用,總有一些需求是隻屬於自己的。這時,就需要造一個只適合自己想法的輪子,根據自己的需求去擴展Ansible的功能。

Ansible允許用戶自定義的方式擴展很多方面的功能,包括:

  • (1).實現某功能的模塊,模塊以作爲任務的方式被執行
  • (2).各類插件,用於調整Ansible的行爲。目前Ansible有12類插件
$ grep 'plugins/' /etc/ansible/ansible.cfg 
#action_plugins     = /usr/share/ansible/plugins/action
#become_plugins     = /usr/share/ansible/plugins/become
#cache_plugins      = /usr/share/ansible/plugins/cache
#callback_plugins   = /usr/share/ansible/plugins/callback
#connection_plugins = /usr/share/ansible/plugins/connection
#lookup_plugins     = /usr/share/ansible/plugins/lookup
#inventory_plugins  = /usr/share/ansible/plugins/inventory
#vars_plugins       = /usr/share/ansible/plugins/vars
#filter_plugins     = /usr/share/ansible/plugins/filter
#test_plugins       = /usr/share/ansible/plugins/test
#terminal_plugins   = /usr/share/ansible/plugins/terminal
#strategy_plugins   = /usr/share/ansible/plugins/strategy

本文會以最簡單易懂的方式介紹模塊的自定義方式(不介紹自定義插件,內容太多)。考慮到有些人沒有編程基礎,這裏我將同時使用Shell和Python來介紹,看菜喫飯即可。

16.1 自定義模塊簡介

可以使用任何一種語言來自定義模塊,只要目標主機能執行即可。這得益於Ansible的運行方式:將模塊的代碼發送到目標節點上調用對應的解釋器執行。

比如可以使用Shell腳本自定義模塊,Ansible執行時會將Shell腳本的內容發送到目標節點上並調用目標節點的shell解釋器(比如bash)去執行這些命令行。

但是絕大多數的模塊都是使用Python語言編寫的(Windows相關模塊除外,它們使用PowerShell編寫)。如果自己定義模塊,按理說也建議使用Python語言,Ansible爲自定義模塊提供了不少非常方便的接口。但是,如果熟悉Shell腳本的話,對於邏輯不太複雜的功能,Shell腳本比Python要方便簡潔,所以不要覺得用Shell寫Ansible模塊上不了檯面。

16.1.1 自定義模塊前須知:模塊的存放路徑

當編寫好自己的模塊後,需要讓Ansible知道在哪裏可以找到它。有三個位置可以存放模塊:

  • (1).playbook文件所在目錄的library目錄內,即pb.yml/../library目錄內
  • (2).roles/Role_Name/library目錄內
  • (3).ansible.cfg中library指令指定的目錄或者環境變量ANSIBLE_LIBRARY指定的目錄

顯然,roles/Role_Name/library目錄內的模塊只對該Role有效,而pb.yml/../library對所有Role和pb.yml有效,ansible.cfg中library指令指定的路徑對全局有效。

16.1.2 自定義模塊前須知:模塊的要求

從第一章節到現在,已經學習了很多模塊,相信對模塊的使用已經非常熟悉。我想各位在使用各個模塊的過程中,應該能感受到這些模塊的一些共同點:

  • (1).每個模塊都有changed、failed狀態
  • (2).絕大多數模塊都可以提供各種各樣的選項參數
  • (3).很多模塊都要求必須有某些選項參數
  • (4).每個模塊都有返回值,從而可以通過register註冊成變量
  • (5).返回值全都是json格式
  • (6).有些模塊具有冪等性
  • (7)....

所以,這跟寫一個腳本或寫一個程序沒什麼區別,僅僅只是在寫這些腳本時多了一些特殊要求。

下面將先以Shell腳本的方式解釋並定義模塊,稍後再演示一個簡單的Python定義的模塊,主要是爲了體現Ansible爲Python自定義模塊所提供的方便的功能。

16.2 Shell腳本自定義模塊(一):Hello World

先使用Shell腳本一個最簡單的自定義模塊,只顯示"hello world"。

下面是腳本模塊的內容,其路徑爲library/say_hello.sh:

#!/bin/bash

echo '{"changed": false, "msg": "Hello World"}'

注意上面的false不要加引號,因爲它要表示的是json的布爾假,加上引號就成了json中的字符串類型。

在library同級目錄下創建一個playbook文件shell_module1.yml,在其中使用該模塊:

---
- hosts: localhost
  gather_facts: no
  tasks: 
    - name: say hello with my module
      say_hello:
      register: res
    
    - debug: var=res
    - debug: var=res.msg

執行:

$ ansible-playbook shell_module1.yml     

PLAY [localhost] *****************************

TASK [say hello with my module] **************
ok: [localhost]

TASK [debug] *********************************
ok: [localhost] => {
    "res": {
        "changed": false,
        "failed": false,
        "msg": "Hello World"
    }
}

TASK [debug] **********************************
ok: [localhost] => {
    "res.msg": "Hello World"
}

PLAY RECAP ************************************
localhost  : ok=3    changed=0    unreachable=0

是否發現自定義模塊好簡單呢?

16.3 Shell腳本自定義模塊(二):簡版file模塊

Ansible的file模塊可以創建、刪除文件或目錄,這裏通過Shell腳本的方式來自定義精簡版的file模塊,稱爲file_by_shell

這個模塊能夠處理相關參數,包括:

  • (1).識別路徑和文件名,選項名稱假設爲path,該選項必須不能省略
  • (2).識別是創建還是刪除操作,選項名稱假設爲state,它只能是兩種值:present或absent,默認是present
  • (3).識別在創建操作時,創建的是文件還是目錄(此處不考慮其它文件類型),如果路徑不存在,這裏決定遞歸創建缺失的目錄,該選項名稱爲type,該選項在創建操作時必須不能省略,它只能有兩種值:file或directory
  • (4).識別在創建操作時,是否給定了權限參數,比如指定要創建的文件權限爲0644,選項名稱爲mode
  • (5).識別在創建操作時,是否給定了owner、group

其它的功能此處不多考慮。

所以,按照常規的腳本調用方式,這個shell腳本的用法大概如下:

例如,Ansible會先把參數以如下方式寫進臨時文件xxx.tmp:

path=PATH state=STATE type=TYPE...

然後將這個臨時文件名傳遞給模塊文件:

file_by_shell.sh xxx.tmp

所以,Shell腳本必須得能夠從這個臨時文件中處理來自playbook中的選項和參數。比如可以像下面這樣獲取path選項的值。

cat xxx.tmp | tr ' ' '\n' | sed -nr 's/^path=(.*)/\1/'

有了這些背景知識,再來寫Shell腳本file_by_shell.sh

#!/bin/bash

############# 定義一個輸出信息到標準錯誤的函數 #############
function err_echo(){ echo "$@" >&2;exit 1; }

############# 選項、參數解析 #############
args="$(cat "$@" | tr ' ' '\n')"
path="$(echo "$args"  | sed -nr 's/^path=(.*)/\1/p')"
type="$(echo "$args"  | sed -nr 's/^type=(.*)/\1/p')"
state="$(echo "$args" | sed -nr 's/^state=(.*)/\1/p')"
mode="$(echo "$args"  | sed -nr 's/^mode=(.*)/\1/p')"
owner="$(echo "$args" | sed -nr 's/^owner=(.*)/\1/p')"
group="$(echo "$args" | sed -nr 's/^group=(.*)/\1/p')"

############# 處理選項之間的邏輯 #############

# path選項:必須存在
[ "$path" ] || { err_echo "'path' argument missing"; }

# state選項:如果不存在,則默認爲present
# 且state只能爲兩種值:present或absent
: ${state="present"}
[ "$(echo $state | sed -r 's/(present|absent)//')" ] && { 
    err_echo "'state' argument error";
}

# type選項:在創建操作時必須存在,且只能爲兩種值:file或directory
if [ "${state}x" == "presentx" ];then
  [ "${type}" ] || { err_echo "'type' argument missing"; }
  
  # type的值只能是:file或directory
  [ "${type}" != "file" ] && [ "${type}" != "directory" ] && { 
    err_echo "'type' argument error";
  }
fi

# mode選項:如果該選項存在,必須是3位或4位數,且每位都是小於7的數值
# 如果該選項不存在,則不管,即按照對應用戶的umask值決定權限
if [ "$mode" ];then
  echo $mode | grep -E '[0-7]?[0-7]{3}' &>/dev/null || {
    err_echo "'mode' argument error";
  }
fi

############# 定義函數,輸出json格式並退出 #############
function echo_json() {
  echo '{ ' $1 ' }'
  # 輸出完後正常退出
  exit
}

############# 刪除文件/目錄的邏輯 #############

# 爲了實現冪等性,先判斷目標是否存在,
# 如果存在,不管是文件還是目錄,都刪除,並設置changed=true
# 如果不存在,什麼也不做,並設置changed=false
# 如果報錯(比如權限不足),無視,Ansible會自動獲取報錯信息
if [ $state == "absent" ];then
  if [ -e "$path" ];then
    rm -rf "$path"
    return_str='"changed": true'
    echo_json "$return_str"
  else
    return_str='"changed": false'
    echo_json "$return_str"
  fi
fi

############# 創建文件/目錄的邏輯 #############

# 爲了實現冪等性,先判斷目標是否存在,
# 如果存在,且類型匹配,則什麼也不做,並設置changed=false
# 如果存在,但類型不匹配(比如想要創建文件,但已存在同名目錄),則報錯  
# 如果不存在,則根據類型創建文件/目錄
# 如果報錯(如權限不足),無視,Ansible會自動獲取報錯信息
if [ $state == "present" ];then
  if [ -e "$path" ];then
    # 文件已存在

    # 獲取已存在文件的類型
    file_type=$(ls -ld "$path" | head -c 1)
    if [ $file_type == "-" -a "$type" == "file" ] || \
       [ $file_type == "d" -a "$type" == "directory" ]
    then
      # 類型匹配
      return_str='"changed": false'
      echo_json "$return_str"
    else
      # 類型不匹配
      err_echo "target exists but filetype error";
    fi
  else
    # 文件/目錄不存在,在此處創建,同時創建缺失的上級目錄
    dir="$(dirname "$path")"
    [ -d "$dir" ] || mkdir -p "$dir"
    [ $type = "file" ] && touch "$path"
    [ $type = "directory" ] && mkdir "$path"

    # 設置權限、owner、group,如果屬性修改失敗,則刪除已創建的目標
    [ "$mode" ]  && { chmod $mode  "$path" || rm -rf "$path"; }
    [ "$owner" ] && { chown $owner "$path" || rm -rf "$path"; }
    [ "$group" ] && { chgrp $group "$path" || rm -rf "$path"; }

    return_str='"changed": true'
    echo_json "$return_str"
  fi
fi

再寫一個playbook文件shell_module.yml來使用該shell腳本模塊:

---
- hosts: localhost
  gather_facts: no
  tasks: 
    - name: use file_by_shell module to create file
      file_by_shell: 
        # 注:父目錄test不存在
        path: /tmp/test/file1.txt
        state: present
        type: file
        mode: 655
    - name: use file_by_shell module to create directory
      file_by_shell: 
        path: /tmp/test/dir1
        state: present
        type: directory
        mode: 755

執行該playbook:

$ ansible-playbook shell_module.yml

PLAY [localhost] *************************************

TASK [use file_by_shell module to create file] *******
changed: [localhost]

TASK [use file_by_shell module to create directory] **
changed: [localhost]

PLAY RECAP *******************************************
localhost     : ok=2    changed=2    unreachable=0

再次執行,因具備冪等性,所以不會做任何操作:

$ ansible-playbook shell_module.yml

PLAY [localhost] ******************************************

TASK [use file_by_shell module to create file] ************
ok: [localhost]

TASK [use file_by_shell module to create directory] *******
ok: [localhost]

PLAY RECAP ************************************************
localhost           : ok=2    changed=0

執行刪除操作:

---
- hosts: localhost
  gather_facts: no
  tasks: 
    - name: use file_by_shell module to remove file
      file_by_shell: 
        path: /tmp/test/file1.txt
        state: absent
    - name: use file_by_shell module to remove directory
      file_by_shell: 
        path: /tmp/test/dir1
        state: absent

執行:

PLAY [localhost] ****************************************

TASK [use file_by_shell module to remove file] **********
changed: [localhost]

TASK [use file_by_shell module to remove directory] *****
changed: [localhost]

PLAY RECAP **********************************************
localhost                  : ok=4    changed=2

16.4 Python自定義模塊

使用Python定義模塊,要簡單一些,一方面是因爲Python的邏輯處理能力比Shell要強,另一方面是因爲Ansible提供了一些方便的Python接口,比如處理參數、處理退出時返回的json數據。

這裏仍然使用Python編寫一個自定義的精簡版的file模塊。

對於初學者來說,使用Python自定義模塊的第一步,是先搭建好腳本的框架:

#!/usr/bin/python

from ansible.module_utils.basic import *

def main():
  ...to_do...

if __name__ == '__main__':
  main()

所有的處理邏輯都在main()函數中定義。

下一步是構造一個模塊對象,並處理Ansible傳遞給腳本的參數。

#!/usr/bin/python

from ansible.module_utils.basic import *

def main():
  md = AnsibleModule(
    argument_spec = dict(
      path  = dict(required=True, type='str'),
      state = dict(choices=['present', 'absent'], type='str', default="present"),
      type  = dict(type='str'),
      mode  = dict(type='int',default=None),
      owner = dict(type='str',default=None),
      group = dict(type='str',default=None),
    )
  )
  params = md.params
  path = params['path']
  state = params['state']
  target_type = params['type']
  # 權限位讀取時是十進制,要將其看作是8進制值而不能是十進制
  mode = params['mode'] and int(str(params['mode']),8)
  owner = params['owner']
  group = params['group']

if __name__ == '__main__':
  main()

上面使用AnsibleModule()構造了一個Ansible模塊對象md,並指定處理的參數以及相關要求。比如要求path必須存在,且其數類型是str,比如state默認值爲present,且只有兩種值可選。關於參數處理,完整的用法參考官方手冊:https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#ansiblemodule

需要注意的是,無法直接在此對各選項之間的邏輯關係進行判斷,例如創建目標時必須指定文件類型。所以,這種邏輯要麼單獨對選項關係做判斷,要麼在執行操作(比如創建文件)時在對應函數中進行判斷或異常處理。

爲圖簡單,我直接在獲取完參數之後立即對它們做判斷:(爲節省篇幅,我省略一部分已編寫的代碼)

#!/usr/bin/python

from ansible.module_utils.basic import *

def main():
  ......
  path = params['owner']
  group = params['group']

  # present時,必須指定type參數
  if state == 'present' and target_type is None:
    raise Exception('type argument missing')
  
if __name__ == '__main__':
  main()

在之後,是定義刪除、創建以及退出時的邏輯:

#!/usr/bin/python

from ansible.module_utils.basic import *

def main():
  ......
  # present時,必須指定type參數
  if state == 'present' and target_type is None:
    raise AnsibleModuleError(results={'msg': 'type argument missing'})
  
  # 如果是absent,直接刪除
  # 否則是創建目標,需要區分要創建的目標類型
  if state == 'absent':
    result = remove_target(path)
  elif target_type == 'file':
    result = create_file(path, mode, owner, group)
  elif target_type == 'directory':
    result = create_dir(path, mode, owner, group)
  
  # 退出,並輸出json數據
  md.exit_json(**result)
  
if __name__ == '__main__':
  main()

最後,就是定義這三個函數:remove_target、create_file、create_dir。因爲這三個函數中都要判斷目標是否存在以及文件類型,所以將判斷是否存在的邏輯也定義成一個函數get_stat

下面是get_stat的代碼:

# 這裏用到了os,所以記得導入os
# 這裏用到了to_bytes,記得導入
# import os
# from ansible.module_utils._text import to_bytes

def get_stat(path):
  b_path = to_bytes(path)
  # 目標存在
  try: 
    if os.path.lexists(b_path):
      if os.path.isdir(b_path):
        return 'directory'
      else:
        return 'file'  # 不管其它類型,此處認爲除了目錄,就是文件
    # 目標不存在
    else:
      return False
  except OSError: 
    raise

下面是remove_target代碼:

# 這裏使用了shutil,所以記得導入shutil
# import shutil

def remove_target(path):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  try:
    # 存在時,刪除
    if target_stat:
      if target_stat == 'directory':
        shutil.rmtree(b_path)
      else:
        # 刪除目錄
        os.unlink(b_path)

      result.update({'changed': True})
    # 不存在時,不管
    else:
      result.update({'changed': False})
  except Exception: 
    raise
  return result

下面是創建普通文件的函數create_file代碼:

def create_file(path, mode=None, owner=None, group=None):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  # 目標存在,則判斷已存在的類型
  # 目標不存在,則創建文件
  if target_stat:
    # 如果已存在的不是普通文件類型,報錯
    if target_stat != 'file':
      raise Exception('target already exists, but type error')
    result.update({'changed': False})
  else:
    # 目標不存在,創建文件
    try:
      # 父目錄是否存在?不存在則遞歸創建
      if not get_stat(os.path.dirname(b_path)):
        os.makedirs(os.path.dirname(b_path))
      open(b_path, 'wb').close()
    except (OSError, IOError):
      raise

    # 決定是否更改權限、owner、group,如果更改出錯,刪除已創建的文件
    try:
      if mode:
        os.chmod(b_path, mode)
      if owner:
        os.chown(b_path, pwd.getpwnam(owner).pw_uid, -1)
      if group:
        os.chown(b_path, -1, pwd.getpwnam(group).pw_gid)
      result.update({'changed': True})
    except Exception:
      if not target_stat:
        os.remove(b_path)
  return result

下面是創建普通目錄的函數create_dir代碼:

def create_dir(path, mode=None, owner=None, group=None):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  # 目標存在,則判斷已存在的類型
  # 目標不存在,則創建目錄
  if target_stat:
    # 如果已存在的不是目錄,報錯
    if target_stat != 'directory':
      raise Exception('target already exists, but type error')
    result.update({'changed': False})
  else:
    # 目標不存在,創建目錄
    try:
      os.makedirs(b_path)
    except (OSError, IOError):
      raise 

    # 決定是否更改權限、owner、group,如果更改出錯,刪除已創建的文件
    try:
      if mode:
        os.chmod(b_path, mode)
      if owner:
        os.chown(b_path, pwd.getpwnam(owner).pw_uid, -1)
      if group:
        os.chown(b_path, -1, pwd.getpwnam(group).pw_gid)
      result.update({'changed': True})
    except Exception:
      if not target_stat:
        shutil.rmtree(b_path)
  return result

將上面所有代碼彙總,得到如下代碼,並保存到文件library/file_by_python.py中:

#!/usr/bin/python

import os
import shutil
from ansible.module_utils.basic import *
from ansible.module_utils._text import to_bytes

def get_stat(path):
  b_path = to_bytes(path)
  # 目標存在
  try: 
    if os.path.lexists(b_path):
      if os.path.isdir(b_path):
        return 'directory'
      else:
        return 'file'  # 不管其它類型,此處認爲除了目錄,就是文件
    # 目標不存在
    else:
      return False
  except OSError: 
    raise

def remove_target(path):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  try:
    # 存在時,刪除
    if target_stat:
      if target_stat == 'directory':
        shutil.rmtree(b_path)
      else:
        # 刪除目錄
        os.unlink(b_path)
      
      result.update({'changed': True})
    # 不存在時,不管
    else:
      result.update({'changed': False})
  except Exception: 
    raise
  return result

def create_file(path, mode=None, owner=None, group=None):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  # 目標存在,則判斷已存在的類型
  # 目標不存在,則創建文件
  if target_stat:
    # 如果已存在的不是普通文件類型,報錯
    if target_stat != 'file':
      raise Exception('target already exists, but type error')
    result.update({'changed': False})
  else:
    # 目標不存在,創建文件
    try:
      # 父目錄是否存在?不存在則遞歸創建
      if not get_stat(os.path.dirname(b_path)):
        os.makedirs(os.path.dirname(b_path))
      open(b_path, 'wb').close()
    except (OSError, IOError):
      raise

    # 決定是否更改權限、owner、group,如果更改出錯,刪除已創建的文件
    try:
      if mode:
        os.chmod(b_path, mode)
      if owner:
        os.chown(b_path, pwd.getpwnam(owner).pw_uid, -1)
      if group:
        os.chown(b_path, -1, pwd.getpwnam(group).pw_gid)
      result.update({'changed': True})
    except Exception:
      if not target_stat:
        os.remove(b_path)
  return result

def create_dir(path, mode=None, owner=None, group=None):
  b_path = to_bytes(path)
  target_stat = get_stat(b_path)
  result = {'path': b_path, 'target_stat': target_stat}
  
  # 目標存在,則判斷已存在的類型
  # 目標不存在,則創建目錄
  if target_stat:
    # 如果已存在的不是目錄,報錯
    if target_stat != 'directory':
      raise Exception('target already exists, but type error')
    result.update({'changed': False})
  else:
    # 目標不存在,創建目錄
    try:
      os.makedirs(b_path)
    except (OSError, IOError):
      raise 

    # 決定是否更改權限、owner、group,如果更改出錯,刪除已創建的文件
    try:
      if mode:
        os.chmod(b_path, mode)
      if owner:
        os.chown(b_path, pwd.getpwnam(owner).pw_uid, -1)
      if group:
        os.chown(b_path, -1, pwd.getpwnam(group).pw_gid)
      result.update({'changed': True})
    except Exception:
      if not target_stat:
        shutil.rmtree(b_path)
  return result

def main():
  md = AnsibleModule(
    argument_spec = dict(
      path  = dict(required=True, type='str'),
      state = dict(choices=['present', 'absent'], type='str', default="present"),
      type  = dict(type='str'),
      mode  = dict(type='int',default=None),
      owner = dict(type='str',default=None),
      group = dict(type='str',default=None),
    )
  )
  params = md.params
  path = params['path']
  state = params['state']
  target_type = params['type']
  # 權限位讀取時是十進制,要將其看作是8進制值而不能是十進制
  mode = params['mode'] and int(str(params['mode']),8)
  owner = params['owner']
  group = params['group']

  # present時,必須指定type參數
  if state == 'present' and target_type is None:
    raise Exception('type argument missing')

  # 如果是absent,直接刪除
  # 否則是創建目標,需要區分要創建的目標類型
  if state == 'absent':
    result = remove_target(path)
  elif target_type == 'file':
    result = create_file(path, mode, owner, group)
  elif target_type == 'directory':
    result = create_dir(path, mode, owner, group)
  
  # 退出,並輸出json數據
  md.exit_json(**result)
if __name__ == '__main__':
  main()

提供一個playbook文件python_module.yml

---
- hosts: localhost
  gather_facts: no
  tags: create
  tasks:
    - name: use file_by_python module to create file
      file_by_python:
        path: /tmp/test/file1.txt
        state: present
        type: file
        mode: 655
    - name: use file_by_python module to create directory
      file_by_python:
        path: /tmp/test/dir1
        state: present
        type: directory
        mode: 755

- hosts: localhost
  gather_facts: no
  tags: remove
  tasks: 
    - name: use file_by_python module to remove file
      file_by_python: 
        path: /tmp/test/file1.txt
        state: absent
    - name: use file_by_python module to remove directory
      file_by_python: 
        path: /tmp/test/dir1
        state: absent

執行創建操作:

$ ansible-playbook --tags create python_module.yml

PLAY [localhost] **************************************

TASK [use file_by_python module to create file] *******
changed: [localhost]

TASK [use file_by_python module to create directory] **
changed: [localhost]

PLAY [localhost] **************************************

PLAY RECAP ********************************************
localhost     : ok=2    changed=2    unreachable=0 

執行刪除操作:

$ ansible-playbook --tags remove python_module.yml

PLAY [localhost] ***************************************

PLAY [localhost] ***************************************

TASK [use file_by_python module to remove file] ********
changed: [localhost]

TASK [use file_by_python module to remove directory] ***
changed: [localhost]

PLAY RECAP *********************************************
localhost                  : ok=2    changed=2 

從結果看,測試是可以通過的。

從上面分別通過Shell腳本和通過Python腳本實現精簡file模塊的兩個示例中可以看到,完全相同的邏輯和功能,Shell腳本定義Ansible模塊時不輸於Python。當然,如果邏輯複雜或者遇到了Shell不好處理的邏輯,使用Python當然要優於Shell。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章