てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] assert モジュールで変数の型チェックする

はじめに

Ansible の変数では様々な型が扱えますが、基本的にゆるめなため、型をチェックしたいことがあるかもしれません。

いままでは、assert モジュールと、type_debug フィルターを組みわせるチェックする方法しか思いつかなかったのが、こちらを拝見したら is string のような書き方もできることを知りました。

せっかくなので、両方のやり方をまとめます。

  • 動作確認環境


[目次]

■ 方法1: type_debug フィルターを利用する

type_debug フィルターは、変数の方を表示するフィルターです。

"{{ 'hello' | type_debug }}" であれば、 str と表示されます。この性質を利用した型チェックです。

検証 Playbook

いくつか型の種類を用意した変数のリストを assert します。ここでは str(文字列)であることをチェックします。

assert では、デバッグ的に success_msgfail_msg を利用します。

- hosts: localhost
  gather_facts: no
  connection: local

  vars:
    targets:    # チェック対象
      - hello
      - 100
      - "100"
      - -100
      - 0.9
      - [1, 2, 3]
      - {name: hoge}

  tasks:
    - name:
      assert:
        that:
          - (item | type_debug) == "str"    # ポイント
        success_msg: "{{ item }} type is {{ item | type_debug }}"
        fail_msg: "{{ item }} type is {{ item | type_debug }}, NOT str"
      loop: "{{ targets }}"

検証結果

結果です。targets で定義した値のうち、hello"100" のみ ok となりました。

$ ansible-playbook -i localhost, assert.yml 

PLAY [localhost] *************************************************************************************************

TASK [assert type] ***********************************************************************************************
ok: [localhost] => (item=hello) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "hello",
    "msg": "hello type is str"          // hello は ok
}
failed: [localhost] (item=100) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug) == \"str\"",
    "changed": false,
    "evaluated_to": false,
    "item": 100,
    "msg": "100 type is int, NOT str"
}
ok: [localhost] => (item=100) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "100",
    "msg": "100 type is str"          // "100" は ok
}
failed: [localhost] (item=-100) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug) == \"str\"",
    "changed": false,
    "evaluated_to": false,
    "item": -100,
    "msg": "-100 type is int, NOT str"
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug) == \"str\"",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "0.9 type is float, NOT str"
}
failed: [localhost] (item=[1, 2, 3]) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug) == \"str\"",
    "changed": false,
    "evaluated_to": false,
    "item": [
        1,
        2,
        3
    ],
    "msg": "[1, 2, 3] type is list, NOT str"
}
failed: [localhost] (item={'name': 'hoge'}) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug) == \"str\"",
    "changed": false,
    "evaluated_to": false,
    "item": {
        "name": "hoge"
    },
    "msg": "{'name': 'hoge'} type is dict, NOT str"
}

PLAY RECAP *******************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

2個目の値 100 も、3個目 "100" も、メッセージ上は 100 と表示され、

    "msg": "100 type is int, NOT str"

とか

    "msg": "100 type is str"

とかになるのが少しややこしいですね。


■ 方法2: is [型名] を利用する

次に、こちらで知った方法です。

検証 Playbook

チェック対象の変数は同じです。

assert で、item is string のように指定します。 str ではなく string です。

- hosts: localhost
  gather_facts: no
  connection: local

  vars:
    targets:
      - hello
      - 100
      - "100"
      - -100
      - 0.9
      - [1, 2, 3]
      - {name: hoge}

  tasks:
    - name: assert type
      assert:
        that:
          - item is string      # ポイント
        success_msg: "{{ item }} type is {{ item | type_debug }}"
        fail_msg: "{{ item }} type is {{ item | type_debug }}, NOT str"
      loop: "{{ targets }}"

検証結果

結果です。hello"100" のみ ok となりました。

$ ansible-playbook -i localhost, assert.yml 

PLAY [localhost] ****************************************************************************************************

TASK [assert type] **************************************************************************************************
ok: [localhost] => (item=hello) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "hello",
    "msg": "All assertions passed"          // hello は ok
}
failed: [localhost] (item=100) => {
    "ansible_loop_var": "item",
    "assertion": "item is string",
    "changed": false,
    "evaluated_to": false,
    "item": 100,
    "msg": "Assertion failed"
}
ok: [localhost] => (item=100) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "100",
    "msg": "All assertions passed"          // "100" は ok
}
failed: [localhost] (item=-100) => {
    "ansible_loop_var": "item",
    "assertion": "item is string",
    "changed": false,
    "evaluated_to": false,
    "item": -100,
    "msg": "Assertion failed"
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "item is string",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "Assertion failed"
}
failed: [localhost] (item=[1, 2, 3]) => {
    "ansible_loop_var": "item",
    "assertion": "item is string",
    "changed": false,
    "evaluated_to": false,
    "item": [
        1,
        2,
        3
    ],
    "msg": "Assertion failed"
}
failed: [localhost] (item={'name': 'hoge'}) => {
    "ansible_loop_var": "item",
    "assertion": "item is string",
    "changed": false,
    "evaluated_to": false,
    "item": {
        "name": "hoge"
    },
    "msg": "Assertion failed"
}

PLAY RECAP **********************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   


■ 型の名前のまとめ

各値の type_debug フィルター適用後の値と、is [型名]ok となる型名をまとめます。

type debug 結果 is [型名]
hello str string
100 int number
"100" str string
-100 int number
0.9 float number
[1, 2, 3] list sequence
{name: hoge} dict sequence / mapping

試した限り、{name: hoge} については、 is sequence でも is mapping でも assertok となったのが意外でした。

is [型名] の方は、Jinja2 の List of Builtin Tests によるもののようです。@satoru_satoh さん、情報ありがとうございます!

また、Jinja2 2.11 では number 以外にも、floatinteger も利用できるようです。


おわりに

型をあまり意識しなくていいことがメリットなこともありますが、つまずくこともあるかもしれません。

型のチェックが必要な時に、参考になれば幸いです。

参考