编程 · 2 3 月, 2025 0

不想写YAML了?用Python手撮Ansible模块并在Playbook中调用

本文也属于《Ansible Playbook速成实战》系列。不废话,让我们直接开始!

什么需求?

在《稍微复杂一些的例子》完成后,老板又提出了大胆的的想法:

保存的文件,文件名能不能加上编号?

稍微想一下就知道,为了实现老板这一句话的想法,playbook需要实现以下功能:

  • 读取文件列表
  • 筛选同类文件,如“info_<数字编号>.log”
  • 获取同类文件最大编号
  • 用“最大编号+1”生成新文件名

好麻烦……与其费尽心机写出惊世骇俗的YAML,还不如直接写Python实现这些功能。

本例中涉及到的知识点

  • 用Python手撮Ansible模块并在playbook中调用

开始整活

1. 创建工作目录

在主目录中增加目录library和Python文件:

pj/ (主目录)
 |- library (!)/
    |- my_create_path.py
 |- (其他目录/文件略)

带“!”的目录,目录名请与下述保持一致。Ansible会自动从中读取所需内容。

2. 手撮Ansible模块

编辑pj/library/my_create_path.py,写入所需功能。

import os
import re
from ansible.module_utils.basic import AnsibleModule
## ^ 此乃事情的关键

def create_new_path(pth_info):
    ## 要求pth_info的内容物:{'dir': xxx, 'prefix': xxx, 'suffix': xxx}
    fname_pattern = re.compile(r'^{}(\d+){}$'.format(pth_info['prefix'], pth_info['suffix']))
    numbers = []

    files = os.listdir(pth_info['dir'])
    for file in files:
        match = fname_pattern.match(file)
        if match:
            number = int(match.group(1))
            numbers.append(number)

    if numbers:
        max_number = max(numbers)
        next_number = max_number + 1
    else:
        next_number = 1
        ## ^ 要是原本目录中没有同类文件则使用编号1

    new_filename = f"{pth_info['prefix']}{next_number}{pth_info['suffix']}"
    new_path = os.path.join(pth_info['dir'], new_filename)
    return new_path

def main():
    module = AnsibleModule(argument_spec=dict(pth_info=dict(type='dict', required=True)))
    ## ^ 不熟悉这种写法的话也可以写成:argument_spec={'pth_info': {'type': 'dict', 'required': True}}
    ## 这段翻译一下就是:调用该模块时,需要传入名为“pth_info”、类型为“dict”的变量,而且是必须的
    ## 至于变量“pth_info”里面应该有什么,那就是具体实现功能的函数需要操心的事了
    result = create_new_path(module.params['pth_info'])
    ## ^ 通过“module.params['pth_info']”获取playbook的传入变量
    module.exit_json(changed=False, result=result)

if __name__ == '__main__':
    main()

3. Playbook中调用手撮的Ansible模块

将以下内容写入用于生成文件名的role。

- name: 生成带编号的文件名
  my_create_path:
  # ^ 使用手撮模块时,模块名称同PY文件名
    pth_info:
    # ^ 这个是手撮模块中定义的名称
      dir: "/tmp/fetch_from_remote"
      prefix: "info_"
      suffix: ".log"
     # ^ 这3个是手撮模块中“create_new_path”函数要求的
  register:
    new_filename
  delegate_to: localhost

大功告成🎉,接下来就可以通过在保存文件的role中使用{{ new_filename.result }}作为文件名保存文件了!