zaki work log

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

[Ansible] 省略したいパラメタに変数指定せざるを得ない場合に使う変数omitとdefaultフィルタ

指定が任意なオプション扱いのパラメタで、指定するパラメタが無い場合は未指定にしたいけど、ループ処理などで決まった型の辞書型変数のリストでキーが有ったり無かったりする場合の処理について。

って書いてもうまく伝わらない気もするのでサンプルコードから。

  - name: create files by vars
    vars:
      files:
        - path: /var/tmp/ansible/var_test
          type: directory
          owner: zaki
          group: zaki
          mode: '0755'
        - path: /var/tmp/ansible/var_test/file1
          type: touch
    ansible.builtin.file:
      state: "{{ item.type }}"
      path: "{{ item.path }}"
      mode: "{{ item.mode | default(omit) }}"
      owner: "{{ item.owner | default(omit) }}"
      group: "{{ item.group | default(omit) }}"
    loop: "{{ files }}"

files変数に、path/type/pwner/group/modeを持った辞書型変数のリストを定義してるけど、1個目のディレクトリ定義は全て指定してるのに対して、2個目のファイル定義はowner/group/modeの指定無しの状態。

これをループ使って1発でansible.builtin.file使ってファイル/ディレクトリ作成しようとしても、mode,owner,groupの定義は2要素目の項目に無いということで

      mode: "{{ item.mode }}"
      owner: "{{ item.owner }}"
      group: "{{ item.group }}"

とplaybookに書いても、2ループ目は以下のように、変数にmode/owner/groupの定義が無いのでエラーになってしまう。

  msg: |-
    The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'mode'

マジック変数omit

こういうときに使うのが、Ansibleのマジック変数omitで、omitが指定されたパラメタは無視され「パラメタ指定なし(省略)」として動作する。

docs.ansible.com

docs.ansible.com

omit単体の動作は以下のコードで確認できる。

  vars:
    data:
      j2_none: "{{ none }}"
      var_omit: "{{ omit }}"
      undef: undefined
      blank: ""
      tilde: ~
      nullval: null

  tasks:
  - name: print
    debug:
      msg: "{{ data }}"

この内容のplaybookで実行すると、出力は以下の通り。

ok: [localhost] => 
  msg:
    blank: ''
    j2_none: ''
    nullval: null
    tilde: null
    undef: undefined

omitをセットしていたvar_omit変数はきれいさっぱり無くなっているのを確認できる。

ついでに、Jinja2テンプレート内(というよりPythonかな?)でnoneを使ってもAnsibleで受け取るとnullになったりせず空文字になり、YAMLnull,~はAnsibleでもnullとなる。

動作はAnsible 2.9.16と2.10.7で確認。

サンプル

github.com

外部ファイル入力などで未定義を表現できないとき

上の例は「辞書で未定義の場合(keyが無い場合)はdefaultフィルタでomitをセット」だったが、例えばAPI実行の結果を入力にしたいけどデータが無い場合は空文字になってるとか、csvファイルを入力にしたいけどカラムは固定なのでデータ無しは空文字扱い、といったパターンもある。

defaultフィルタの通常動作は「フィルタの入力変数が未定義の場合は引数の変数をセット」という動作のため、変数は存在するけどセットされている変数が0(int)false(bool)や空文字やnullの場合は反応しない。
これらの「偽」の場合にdefaultフィルタを使って値をセットするには、引数の第2引数にtrueをセットする。

jinja.palletsprojects.com

If you want to use default with variables that evaluate to false you have to set the second parameter to true:

{{ ''|default('the string was empty', true) }}

docs.ansible.com

If you want to use the default value when variables evaluate to false or an empty string you have to set the second parameter to true:

{{ lookup('env', 'MY_USER') | default('admin', true) }}

これを使えば、入力csvファイルでファイルとプロパティ一覧を以下のように作成し、

path,type,owner,group,mode
/var/tmp/ansible/csv_test,directory,zaki,zaki,0755
/var/tmp/ansible/csv_test2,directory,,,
/var/tmp/ansible/csv_test/file1,touch,,,

playbookではansible.builtin.read_csvを使って

  tasks:
  - name: read csv file
    ansible.builtin.read_csv:
      path: filelist.csv
    register: res_input

  - name: create files by csv
    ansible.builtin.file:
      state: "{{ item.type }}"
      path: "{{ item.path }}"
      mode: "{{ item.mode | default(omit, true) }}"
      owner: "{{ item.owner | default(omit, true) }}"
      group: "{{ item.group | default(omit, true) }}"
    loop: "{{ res_input.list }}"

みたいな実装をすることで、modeやowner指定が無い場合は省略してファイル作成ができる。

サンプル

github.com


ちなみに、fileモジュールのownergroupnull指定してもエラーになるが、modeについてはnull指定するとパラメタは無視されてデフォルト動作になるっぽい。

docs.ansible.com

この辺りの動作はモジュールに依存するので動作はよく確認すること。