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)
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のドキュメント参照
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
フィルターを使用します。
使い方はPythonのstrftime()
メソッドとほぼ同様で、このフィルターは「フォーマット文字列に対してフィルタリング」して日時の文字列に変換します。
- 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) }}"
参考(フィルター作成)
- Filter plugins
- 実装例: core.py