zaki work log

作業ログやら生活ログやらなんやら

[Ansible] 日付書式の文字列から「〇分後」を求める (既存フィルター or カスタムフィルター)

Ansibleで「指定日時の5分後」みたいな時刻加減算をやる機会があったけど、ちょうど良い機能が見当たらなかったのでいろいろ試した話。
だいぶ面倒なことをやってるので「〇〇すればシュッとできるよ」とか情報があれば教えてください。

サンプルコードの日付が古いのは、8月末に一度書きかけてたけどPCのディスクが完全に死んでしまい、そこから書き直したためです(笑)

Pythonの場合の日時の加減算

Pythonで日時を扱う場合はdatetimeオブジェクトを使います。
このオブジェクトに「5分後」や「3日前」のような加減算を行うにはdatetime.timedelta()を使います。

# 日付の文字列からdatetimeを取得
date_str = '2023-08-31 23-58-43'
format_str = '%Y-%m-%d %H-%M-%S'
dt = datetime.datetime.strptime(date_str, format_str)
print(dt)

# 「5分後」を求める
r = dt + datetime.timedelta(minutes=5)
print(r)

実行結果は以下の通り。

2023-08-31 23:58:43
2023-09-01 00:03:43

5分30秒なら以下のように値をセットします。

datetime.timedelta(minutes=5, seconds=30)

docs.python.org

Ansibleの場合

Ansibleで上のようなことをやりたかったけど、datetimeオブジェクトの生成まではto_datetimeフィルターが使えるけど、timedeltaが使えない。(たぶん)
なので地道に、datetimeオブジェクトを数値型のUNIXエポックからの秒に変換→秒単位で加減算→datetimeオブジェクトに戻す、という処理で実装しました。

datetimeオブジェクトの取得

現在日時と、指定日付の文字列の場合の例。

now()

now()は現在時刻のdatetimeオブジェクトを得る関数。

    - ansible.builtin.debug:
        msg: "{{ now() }}"  # 現在日時

ちなみにnow()ってFQCNとかってあるのかな…

to_datetime()

to_datetime()は日時を表現した任意の文字列に対してフォーマット文字列を指定しdatetimeオブジェクトを得る。

    - ansible.builtin.debug:
        msg: "{{ '2023-08-29 09:12:51' | ansible.builtin.to_datetime('%Y-%m-%d %H:%M:%S') }}"

使用可能なフォーマット文字列はPythonのドキュメント参照

docs.python.org

datetimeオブジェクトから秒を得る

datetimeクラスのtimestamp()メソッドを使う。float型。

    - ansible.builtin.debug:
        msg: "{{ ('2023-08-29 09:12:51' | ansible.builtin.to_datetime('%Y-%m-%d %H:%M:%S')).timestamp() }}"

秒になれば加算・減算は自由にできるので、例えば「5日後」であれば、60*60*24*5を加算する。

    - ansible.builtin.debug:
        msg: "{{ ('2023-08-29 09:12:51' | ansible.builtin.to_datetime('%Y-%m-%d %H:%M:%S')).timestamp() + 60*60*24*5 }}"

秒から任意の日時フォーマットの文字列を得る

秒から日時の書式の文字列を得るにはstrtimeフィルターを使用します。
使い方はPythonstrftime()メソッドとほぼ同様で、このフィルターは「フォーマット文字列に対してフィルタリング」して日時の文字列に変換します。

    - ansible.builtin.debug:
        msg: "{{ '%Y-%m-%d %H:%M:%S' | ansible.builtin.strftime(('2023-08-29 09:12:51' | ansible.builtin.to_datetime('%Y-%m-%d %H:%M:%S')).timestamp() + 60*60*24*5) }}"

フィルタープラグインで処理する場合

上記の処理が1か所くらいであれば気にならないけれど、頻出するのであればその都度複数のフィルター実行や計算を行っているとYAMLの可読性の高さが損なわれるので、カスタムフィルターとしてPythonコードで外部実装するとplaybookがスッキリします。

「何分後」を処理するフィルタープラグインadd_minsの実装例は以下。

import datetime

def add_mins(date_str, format_str, minutes):
    dt = datetime.datetime.strptime(date_str, format_str)
    return dt + datetime.timedelta(minutes)

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

上のPythonスクリプトを任意のパスに保存し、スクリプトのあるディレクトリをansible.cfgなどにDEFAULT_FILTER_PLUGIN_PATHを設定するか、実行時に環境変数ANSIBLE_FILTER_PLUGINSを設定してAnsibleを実行します。

このフィルターを使ったplaybookは以下。ややこしい何段階かのフィルター処理を全部フィルタープラグインに移動したので、何をしているかが一目瞭然。
もしフォーマットも固定で良いなら引数から削除しても良いかも。

    - ansible.builtin.debug:
        msg: "{{ '2023-08-29 09:12:51' | add_mins('%Y-%m-%d %H:%M:%S', 5) }}"

参考(フィルター作成)