コンテンツにスキップ

フィルタ

ドキュメントはAnsibleとJinja2両方チェックする。

項目やコレクションによっては個別のドキュメントもある。

実装はこのあたり

デフォルト値 (default)

未定義だったらこの値に置き換える、というもの。
以下はfoobar_valuesが未定義の場合はvaluezzzとなる。

vars:
  value: "{{ foobar_values | default('zzz') }}"

よく使うのは初期値が無い場合にループでリストを作るとき。

    - name: curry
      debug:
        msg: "{{ item }}"
      loop: "{{ yasai + (niku | default([])) }}"

とか

    - name: set_fact list value
      vars:
        list_values:
        - item1
        - item2
        - item3
      set_fact:
        list_items: "{{ list_items|default([]) + [item] }}"
      loop: "{{ list_values }}"

未定義でなくfalseだったら置き換えるのであれば、第2引数にtrueをセットする。

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

三項演算子

ternaryを使う。

Defining different values for true/false/null (ternary)

    - name: ternary sample
      debug:
        msg: "{{ (sample_value == 1) | ternary('sample_valueは1だよ', 'sample_valueは1じゃないよ') }}"
      # やってることは (sample_value == 1) ? 'sample_valueは1だよ': 'sample_valueは1じゃないよ' と同じ

    - name: ternary nest sample
      debug:
        msg: "{{ (sample_value == 1) |
                    ternary('sample_valueは1だよ',
                              (sample_value == 2) | ternary('sample_valueは2だよ', 'sample_valueは1でも2でもないよ')
                            )
              }}"
        # 入れ子にしたternary()

    - name: ternary nest sample
      debug:
        msg: "{{ (sample_value == 1) |
                    ternary('sample_valueは1だよ', undefined) |
                  default((sample_value == 2) |
                           ternary('sample_valueは2だよ', 'sample_valueは1でも2でもないよ')
                         )
              }}"
      # 入れ子にせずに、defaultフィルタを併用。ピタゴラスイッチ感。
      # if-elsif-else っぽく見せるインデント難しい

辞書を{ key=辞書のキー名, value=辞書のvalueデータ }のリストに変換

dict2itemsを使う。

Transforming dictionaries into lists

---
- hosts: localhost
  gather_facts: false

  tasks:
  - name: dict2items sample
    vars:
      menu:
        curry:
          vegetable:
            - carrot
            - potato
            - onion
          meat:
            - cow
          chicken:
        pasta:
          vegetable:
            - spinach
            - garlic
    debug:
      msg: "{{ menu | dict2items }}"

実行結果は以下の通り。

"msg": [
    {
        "key": "curry",
        "value": {
            "chicken": null,
            "meat": [
                "cow"
            ],
            "vegetable": [
                "carrot",
                "potato",
                "onion"
            ]
        }
    },
    {
        "key": "pasta",
        "value": {
            "vegetable": [
                "spinach",
                "garlic"
            ]
        }
    }
]

ansible-sample/dict2items.yml at master · zaki-lknr/ansible-sample

検索 (select / selectattr)

[Ansible / Jinja2] select / selectattr を使った配列と辞書のフィルタリング - zaki work log

リスト要素への一律処理 (map)

別フィルタの適用

"{{ items | map('length') }}"

リストitemsの各要素に対してlengthフィルタをかます

要素抜き出し

辞書のキーをattributeで指定することで、各要素(辞書型からなる)の指定キーの値のみ抜き出す。

"{{ items | map(attribute='hostname') }}"

キーが無い場合はAnsibleUndefinedになるが、オプションか更にフィルタすることで対処できる。
defaultを指定すればキーが無い場合はその値に置き換える。

"{{ items | map(attribute='hostname', default='no data') }}"

selectを使ったフィルタに繋げれば、undefの項目を除外もできる。

"{{ items | map(attribute='hostname') | select('none') }}"

noneJinja2のTestの一つ。

リストの先頭・末尾要素

array[0], array[-1]でも一緒といえば一緒だけど。
出展はJinja2のフィルタ。

    - name: first
      vars:
        list:
          - item1
          - item2
          - item3
        foobar: hogehoge
      debug:
        msg:
          - "{{ list | first }}"
          - "{{ list[0] }}"
          - "{{ list | last }}"
          - "{{ list[-1] }}"

結果

TASK [first] *******************************************
ok: [localhost] => 
  msg:
  - item1
  - item1
  - item3
  - item3

ちなみに、文字列型の場合でもiterableなので、一文字目・最後の文字が取れる。

リストから要素の除外(difference)

リストから特定要素を除外するにはdifferenceフィルタを使う。
入力のリストの要素から、differenceフィルタの引数に指定したリストの要素を除外したリストを返す。
differenceにもリストを指定する必要がある(iterableな変数でなければエラーとなる)

---
- hosts: localhost
  vars:
    int_list1: [1, 2, 3]
    int_list2: [3, 4]

  tasks:
  - name: defference filter sample
    debug:
      msg:
      - "{{ int_list1 | difference(int_list2) }}"

出力は[1, 2]になる。

なお、文字列の場合は元々iterableのため、differenceの引数はリストでなくても一応動作する。

---
- hosts: localhost
  vars:
    list1: ["foo", "bar", "baz"]
    list2: ["baz", "qux"]
    str_item: "baz"

  tasks:
  - name: defference filter sample
    debug:
      msg:
      - "{{ list1 | difference(list2) }}"
      - "{{ list1 | difference(str_item) }}"

どちらの結果も["foo", "bar"]になる。

辞書のマージ(combine)

- name: merge 2 dicts
  vars:
    var1:
      list1:
        - item1
        - item2
    var2:
      list2:
        - itemA
        - itemB
  debug:
    msg: "{{ var1 | combine(var2) }}"

2つの辞書をマージする。
重複キーの無い辞書同士は単純合成になり、上のタスクの実行結果は以下の通り。

TASK [merge 2 dicts] *********************************************
ok: [localhost] => {
    "msg": {
        "list1": [
            "item1",
            "item2"
        ],
        "list2": [
            "itemA",
            "itemB"
        ]
    }
}

同じキーを含む場合は上書きする。

- name: merge 2 dicts
  vars:
    var1:
      list1:
        - item1
        - item2
    var2:
      list1:
        - item3
      list2:
        - itemA
        - itemB
  debug:
    msg: "{{ var1 | combine(var2) }}"

combineする辞書に同じlsit1キーを含む場合、このタスクの実行結果は以下の通りで、combineに指定した辞書側のlist1キーの値で上書きされる。

TASK [merge 2 dicts] ******************************************************************
ok: [localhost] => {
    "msg": {
        "list1": [
            "item3"
        ],
        "list2": [
            "itemA",
            "itemB"
        ]
    }
}

list_merge

辞書の要素がリストの場合、キーが同じ場合の上書き動作のデフォルトは、list_mergeパラメタで動作を変更できる。

appendを指定すると、要素の末尾に追加。

- name: merge 2 dicts
  vars:
    var1:
      list1:
        - item1
        - item2
    var2:
      list1:
        - item3
      list2:
        - itemA
        - itemB
  debug:
    msg: "{{ var1 | combine(var2, list_merge='append') }}"

list1キーが重複した入力で、list_merge=appendを指定した場合の実行結果は以下の通り。

TASK [merge 2 dicts] *********************************************
ok: [localhost] => {
    "msg": {
        "list1": [
            "item1",
            "item2",
            "item3"
        ],
        "list2": [
            "itemA",
            "itemB"
        ]
    }
}

list1キーの値のリストもマージ(combimeの引数に指定する方が末尾に追加)されている。
list_mergeに指定できるパラメタは以下の通り。

パラメタ 動作
append 末尾に追加
prepend 先頭に追加
append_rp 末尾に追加 (重複時に既存要素は削除)
prepend_rp 先頭に追加 (重複時に既存要素は削除)

append_rpの動作は以下の通り。

- name: merge 2 dicts
  vars:
    var1:
      list1:
        - item1
        - item2
    var2:
      list1:
        - item3
        - item1
        - item4
      list2:
        - itemA
        - itemB
  debug:
    msg: "{{ var1 | combine(var2, list_merge='append_rp') }}"

このタスクを実行すると以下の通り。
リスト要素にどちらもitem1が含まれるためこれがvar1から削除され、var2の要素(item3,item1,item4)が末尾へ追加される。

TASK [merge 2 dicts] *********************************************
ok: [localhost] => {
    "msg": {
        "list1": [
            "item2",
            "item3",
            "item1",
            "item4"
        ],
        "list2": [
            "itemA",
            "itemB"
        ]
    }
}

prepend_rpの動作は、↑と同じ条件でパラメタのみ変更して実行すると以下の通り。
重複するitem1var1から削除される動作は同じで、var2の要素(item3,item1,item4)が先頭へ追加される。

TASK [merge 2 dicts] *********************************************
ok: [localhost] => {
    "msg": {
        "list1": [
            "item3",
            "item1",
            "item4",
            "item2"
        ],
        "list2": [
            "itemA",
            "itemB"
        ]
    }
}

パス処理

パスの合成 (path_join)

path_joinを使うと、リストの文字列をディレクトリセパレータで繋ぐ。

- name: create repository url
  debug:
    msg:
    - "{{ (registry_url, registry_username, 'awx') | path_join }}"
registry_username = zaki-lknr
registry_url = ghcr.io

という変数が設定されていれば、実行結果は以下の通り。

TASK [create repository url] ************************************
ok: [oci-g-a1-ubuntu] => 
  msg:
  - ghcr.io/zaki-lknr/awx

暗号化パスワード

password_hash

password: "{{ password_plain_text | password_hash('sha512') }}"

デフォルトはsha512で暗号化される。
https://github.com/ansible/ansible/blob/v2.10.5/lib/ansible/plugins/filter/core.py#L266

def get_encrypted_password(password, hashtype='sha512', salt=None, salt_size=None, rounds=None):

saltの指定が無い場合はランダムになるため、実行のたびに結果の値は変化する。(つまり、このパスワードを設定したりすれば毎回changedになる)
冪等にしたければ、saltに固定の値をセットする。

password: "{{ password_plain_text | password_hash('sha512', 'hoge') }}"

base64

エンコード

- name: base64 enc
  vars:
    sample_text: zaki
  debug:
    msg: "{{ sample_text | b64encode }}"

ok: [localhost] => 
  msg: emFraQ==

デコード

- name: base64 dec
  vars:
    b64text: emFraQ==
  debug:
    msg: "{{ b64text | b64decode }}"

ok: [localhost] => 
  msg: zaki

オブジェクトの型を調べる (type_debug)

任意のオブジェクトの型をチェックするには type_debug を使う。

- name: type debug
  debug:
    msg: "{{ item }}"
  vars:
    num_val: 1
    str_val: zzz
    list_val:
      - item1
      - item2
      - item3
    dict_val:
      key1: val1
      key2: val2
      key3: val3
  loop:
    - "{{ num_val | type_debug }}"
    - "{{ str_val | type_debug }}"
    - "{{ list_val | type_debug }}"
    - "{{ dict_val | type_debug }}"

結果

TASK [type debug] ***********************************************************
ok: [localhost] => (item=int) => 
  msg: int
ok: [localhost] => (item=AnsibleUnicode) => 
  msg: AnsibleUnicode
ok: [localhost] => (item=list) => 
  msg: list
ok: [localhost] => (item=dict) => 
  msg: dict