うさラボ

お勉強と備忘録

Ansibleのカスタムフィルターを作る

この記事はエーピーコミュニケーションズ Advent Calendar 2022の16日目の記事です qiita.com

やりたいこと

Xmasといったらサンタですね。

大人になってからというものサンタにあっていません。

豊富なモジュールやフィルタがあるAnsibleでも
サンタに会えるモジュール・フィルタはありませんでした。

せっかくなので
自作のフィルタ(カスタムフィルター)を作成してサンタと会いたいと思います。

スクリプト書く

いきなりAnsibleのフィルタにするのではなく、まずはPythonスクリプトでJinja2Templateを呼び出し
その中で自作したXmasフィルタを動かします。

xmas.py

/で区切ったYYYY/MM/DDのデータを受け取り
MMが12、DDが25の場合のみサンタに会えるフィルタを作成します。

import datetime

def Xmas(*args, **kwargs):
  split_arg = args[0].split('/')
  try:
    date = datetime.date(int(split_arg[0]),int(split_arg[1]),int(split_arg[2]))
    if date.month == 12 and date.day == 25:
      res = '''
           [ 内緒 ]
            '''
      return res
    else:
      res = '''
        サンタかと思ったか?馬鹿め!それは残像だ!
         -= ∧ ∧
        -=と( ・∀・)
         -=/ と_ノ
        -=_//⌒ソ
      '''
      return res
  except IndexError:
    return args[0]

処理は簡単に、受け取った文字列を/で分割して12月25日を判断しています。
[ 内緒 ]の部分にはサンタのAAが入っていますが、最後の表示の楽しみということにします。

script.py

from jinja2 import Environment, FileSystemLoader
from xmas import Xmas

if __name__ == '__main__':
  j2_env = Environment(loader=FileSystemLoader('template/',encoding='utf-8'))
  j2_env.filters['Xmas'] = Xmas

  template = j2_env.get_template('stdout.j2')
  render = template.render(data="2022/12/15")

  print(render)

j2_env.filter[<フィルタ名>] = 関数と定義することで自作のフィルターをテンプレートで利用可能となります
dataは"2022/12/15"のため、まだサンタに会えないのが想定になります

template/stdout.j2

{{ data | Xmas }}

ディレクトリ内は以下のような配置になっています

.
├── script.py
├── template
│   └── stdout.j2
└── xmas.py

スクリプトを実行する


まだサンタには会えていませんが、自作したフィルタが動くことが確認できました。

カスタムフィルターにする

続いて、作成したカスタムフィルタをAnsibleで読み込めるようにします

xmas.py

import datetime

def Xmas(*args, **kwargs):
  split_arg = args[0].split('/')
  try:
    date = datetime.date(int(split_arg[0]),int(split_arg[1]),int(split_arg[2]))
    if date.month == 12 and date.day == 25:
      res = '''
           [ 内緒 ]
            '''
      return res
    else:
      res = '''
        サンタかと思ったか?馬鹿め!それは残像だ!
         -= ∧ ∧
        -=と( ・∀・)
         -=/ と_ノ
        -=_//⌒ソ
      '''
      return res
  except IndexError:
    return args[0]

class FilterModule(object):
  def filters(self):
      return {
          'Xmas': Xmas,
      }

class FilterModuleを追記します。
関数filtersの戻り値でJinja2で利用可能なフィルタ名とスクリプトないの関数を紐つけています。
書き方はbuiltinのcore.pyを参考にしています
docs.ansible.com

github.com

Ansibleで実行する

さて、これでカスタムフィルターは作成できました
続けて実際にPlaybook内で自作したフィルターを呼び出してみたいと思います。

---
- name: Xmas?
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    date: 2022/12/15

  tasks:
    - name: Xmas?
      debug:
        msg: "{{ date | Xmas }}"

実行してみる

エラーになりました、 自作したカスタムフィルタを使うにはansible.cfgでfilter_pluginsを指定する必要があります

[defaults]
filter_plugins = .

再実行


カスタムフィルターは動きましたが、一行で表示されているためせっかくのAAが確認できません

せっかくなのでstdout_callback_pluginをYAMLにします
yamlコールバックプラグインcommunity.generalコレクションに入っています。ない場合は事前にインストールします

[defaults]
filter_plugins = .
stdout_callback = community.general.yaml

再々実行
バッチリですね

インプットは12/15なのでクリスマスではなくサンタには会えませんでした。

幸せになる

未来を先取りして、変数を12/25にします

---
- name: Xmas?
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    date: 2022/12/25

  tasks:
    - name: Xmas?
      debug:
        msg: "{{ date | Xmas }}"

実行してみます

   サンタに会えました。最高です。

おまけ

  • ansible.cfgをいじりたくない場合はデフォルトでfilter参照するパスに直接配置します
ansible-config dumps
DEFAULT_FILTER_PLUGIN_PATH(default) = ['/Users/kouta/.ansible/plugins/filter', '/usr/share/ansible/plugins/filter']

フィルターを配置

(v_ansible) MBA:CustomFilter_2022 kouta$ cp xmas.py /Users/kouta/.ansible/plugins/filter/xmas.py
  • collectionにして配布したい ansible-collectionにして配布したい場合は、ansible-galaxy collection init <コレクション名>で生成されたディレクトリのplugins/filter配下に自作のフィルターを配置します
    usalab.xmasコレクションを作成し、スクリプトを配置しました

コレクションを配置したパスをansible.cfgで指定します。 指定したパスのディレクトは以下のような構成になっている必要があるようです
<指定したパス>/ansible_collection/<自作したコレクション>

[defaults]
filter_plugins = filter_plugins
stdout_callback = community.general.yaml
collections_paths = collections

せっかくなので、filterに引数が追加で渡されるパターンで拡張してみます 利用イメージとしては、以下のようにフィルターを使うにあたって2つの引数がある(1つめdate,2つめ'santa') {{ date | Xmas('santa') }}

第2引数の文字列がsantaもしくはreindeer(トナカイ)の文字列に応じて返す値を変えます

import datetime

def Xmas(*args, **kwargs):
  split_arg = args[0].split('/')
  key = args[1]
  try:
    date = datetime.date(int(split_arg[0]),int(split_arg[1]),int(split_arg[2]))
    if date.month == 12 and date.day == 25:
      if key == 'santa':
        res = '''
              [内緒]
              '''
      elif key == 'reindeer':
        res = '''
              [内緒]
              '''
      return res
    else:
      res = '''
        サンタかと思ったか?馬鹿め!それは残像だ!
         -= ∧ ∧
        -=と( ・∀・)
         -=/ と_ノ
        -=_//⌒ソ
      '''
      return res
  except IndexError:
    return args[0]

class FilterModule(object):
  def filters(self):
      return {
          'Xmas': Xmas,
      }

プレイブックは以下のように記載します usalab.xmasにあるXmasフィルタを呼び出します

---
- name: Xmas?
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    date: 2022/12/25

  tasks:
    - name: Xmas?_サンタ
      debug:
        msg: "{{ date | usalab.xmas.Xmas('santa') }}"

    - name: Xmas?_トナカイ
      debug:
        msg: "{{ date | usalab.xmas.Xmas('reindeer') }}"

バッチリ動きますね

※ansible.cfgでcollections_pathを指定した場合、指定したパスに存在するコレクションのみが対象になるようです、コールバックプラグインで利用しているcommunity.generalコレクションもcollections_pathで指定したディレクトリに含める必要があります。