default(omit)したいのに空文字が定義されてしまうケースをternaryで解決する
2021/05/31追記
ternary
フィルターを使わなくても、default
フィルターの標準機能でやりたいことが実現できました。default
フィルターの引数にtrue
を指定することで、対象の変数がfalse
と評価される場合にdefault
を適用することができます。
以下、サンプルコードと実行結果。
--- - hosts: localhost gather_facts: false connection: local vars: foo: "" tasks: - name: debug debug: msg: "{{ item }}" loop: - "{{ foo | bool }}" - "{{ foo | default(omit) }}" - "{{ foo | default(omit,true) }}"
PLAY [localhost] ********************************************************************************************************************************************************************** TASK [debug] ************************************************************************************************************************************************************************** ok: [localhost] => (item=False) => msg: false ok: [localhost] => (item=) => msg: '' ok: [localhost] => (item=__omit_place_holder__34382e50c55cdbf84ac5c85dfb41cba3629ba8f8) => msg: Hello world! PLAY RECAP **************************************************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
参考
以下蛇足。
はじめに
default
フィルターを使用することで、変数が定義されていない場合のデフォルト値を指定できます。さらに、デフォルト値にomit
を指定することで、パラメータそのものを省略することができます。
ただし、default(omit)
で省略されるのは、変数が未定義であった場合です。例えば、read_csv
モジュールで空のフィールドを含むCSVファイルを読み込んで、debug
モジュールで出力した場合、下記のようになります。
path,mode /tmp/foo, /tmp/bar, /tmp/baz,0444
ok: [localhost] => { "res_csv": { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "dict": {}, "failed": false, "list": [ { "mode": "", "path": "/tmp/foo" }, { "mode": "", "path": "/tmp/bar" }, { "mode": "0444", "path": "/tmp/baz" } ] } }
CSVファイル上、/tmp/foo
と/tmp/bar
のmode
は空ですが、変数としては空文字""
が定義されています。したがって、default
フィルタは動作しません。上記のケースで空文字""
をomit
するため、ternary
フィルタを利用します。
Playbook
- hosts: localhost gather_facts: false connection: local tasks: - name: Read CSV file community.general.read_csv: path: file.csv register: res_csv - name: Touch files with an optional mode ansible.builtin.file: path: "{{ item.path }}" state: touch mode: "{{ (item.mode == '') | ternary(omit, item.mode) }}" loop: "{{ res_csv.list }}" register: res_file - name: Debug res_file ansible.builtin.debug: msg: "{{ item.invocation.module_args }}" loop: "{{ res_file.results }}" loop_control: label: "{{ item.invocation.module_args.path }}"
実行結果
PLAY [localhost] ***************************************************************************************************************************** TASK [Read CSV file] ************************************************************************************************************************* ok: [localhost] TASK [Touch files with an optional mode] ***************************************************************************************************** changed: [localhost] => (item={'path': '/tmp/foo', 'mode': ''}) changed: [localhost] => (item={'path': '/tmp/bar', 'mode': ''}) changed: [localhost] => (item={'path': '/tmp/baz', 'mode': '0444'}) TASK [Debug res_file] ************************************************************************************************************************ ok: [localhost] => (item=/tmp/foo) => { "msg": { "_diff_peek": null, "_original_basename": null, "access_time": null, "access_time_format": "%Y%m%d%H%M.%S", "attributes": null, "follow": true, "force": false, "group": null, "mode": null, ★ "modification_time": null, "modification_time_format": "%Y%m%d%H%M.%S", "owner": null, "path": "/tmp/foo", "recurse": false, "selevel": null, "serole": null, "setype": null, "seuser": null, "src": null, "state": "touch", "unsafe_writes": false } } ok: [localhost] => (item=/tmp/bar) => { "msg": { "_diff_peek": null, "_original_basename": null, "access_time": null, "access_time_format": "%Y%m%d%H%M.%S", "attributes": null, "follow": true, "force": false, "group": null, "mode": null, ★ "modification_time": null, "modification_time_format": "%Y%m%d%H%M.%S", "owner": null, "path": "/tmp/bar", "recurse": false, "selevel": null, "serole": null, "setype": null, "seuser": null, "src": null, "state": "touch", "unsafe_writes": false } } ok: [localhost] => (item=/tmp/baz) => { "msg": { "_diff_peek": null, "_original_basename": null, "access_time": null, "access_time_format": "%Y%m%d%H%M.%S", "attributes": null, "follow": true, "force": false, "group": null, "mode": "0444", ★ "modification_time": null, "modification_time_format": "%Y%m%d%H%M.%S", "owner": null, "path": "/tmp/baz", "recurse": false, "selevel": null, "serole": null, "setype": null, "seuser": null, "src": null, "state": "touch", "unsafe_writes": false } } PLAY RECAP *********************************************************************************************************************************** localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実行結果のmodule_args
を見ると、/tmp/foo
と/tmp/bar
のmode
が、""
でなくnull
になっています。これは、ternary
フィルタで評価した結果、omit
が利いていることを示します。ぶっちゃけfile
モジュールはmode
パラメータの指定が""
でも動作しますが、空文字の指定が許されないパラメータを持つモジュールでは有用なシーンがあります。