てくなべ (tekunabe)

ansible / network / automation

[Ansible] 変数の値が指定の範囲内の数値であることを assert モジュールでチェックする


これは Ansible 2 Advent Calendar 2019 の3日目の記事です。

■ はじめに

assert モジュールthat オプションで >=<= などを利用することで、数値の範囲をチェックできます。

この記事では、簡単なサンプルをもとに説明します。



■ 【要件1】 1〜1000 であることをチェックする (int も float も ok)

まずは、1〜1000 の数値( int も float も ok)であることをチェックする方法です。

数値 x のをチェックしたい場合、assert モジュールの that には以下のように指定します。

- name: range check
  assert:
    that: 
      - 1 <= x
      - x <= 1000

1 <= item <= 1000 のように書きたくなるかもしれませんが、残念ながらできません。その代わり、複数の AND 条件として指定します。

x の各値と assert の結果は以下のようになります。

x の値 assert の判定 備考
1 ok
1.0 ok float
999.9 ok float
1000 ok
1_000 ok 桁区切りリテラル
1000.0 ok
0b1111101000 ok 1000 の 2進数表記
01750 ok 1000 の 8進数表記
0x3E8 ok 1000 の 16進数表記
-1 failed
0 failed
0.9 failed float
1000.1 failed float
1001 failed float
0b1111101001 failed 1001 の 2進数表記
01751 failed 1001 の 8進数表記
0x3E9 failed 1001 の 16進数表記

検証

実態に試してみます。

Playbook

以下の Playbook を利用します。 数値のパターンをいろい試すため、ok になる想定の値と、failed になる想定の値をそれぞれリストにして、それぞれ loop でチェックします。

- hosts: localhost
  gather_facts: no

  vars:
    ok_patterns:
      - 1
      - 1.0
      - 999.9
      - 1000
      - 1_000
      - 1000.0
      - 0b1111101000   # 1000
      - 01750          # 1000
      - 0x3E8          # 1000 
    failed_patterns:
      - -1
      - 0
      - 0.9
      - 1000.1
      - 1001
      - 0b1111101001   # 1001
      - 01751          # 1001
      - 0x3E9          # 1001

  tasks:
    - name: ok pattern
      assert:
        that: 
          - 1 <= item
          - item <= 1000
      loop: "{{ ok_patterns }}"
      
    - name: failed patterns
      assert:
        that:
          - 1 <= item
          - item <= 1000
      loop: "{{ failed_patterns }}"

実行結果

Playbook を実行します。failed 時は、どの条件に引っかかったか表示されます。

$ ansible-playbook -i localhost, validate/range1.yml 

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

TASK [ok pattern] *******************************************************************************************************
ok: [localhost] => (item=1) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1.0) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1.0,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=999.9) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 999.9,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000.0) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000.0,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}

TASK [failed patterns] **************************************************************************************************
failed: [localhost] (item=-1) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": -1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.1) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}

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

1〜1000 の数値( float も ok)であることをチェックできました。


■ 【要件2】 1〜1000 の int であることをチェックする

次の要件は、数値の範囲に加えて、int であることのチェックもします。

assert の書き方としては、要件1の条件に加えて、type_debug フィルターを利用して型チェックをします。

- name: range check
  assert:
    that: 
      - (item | type_debug ) == "int"  # 型チェック
      - 1 <= x
      - x <= 1000

これにより、要件1 のときは異なり、数値的には範囲内だが int でない 1.0 のような値は failed になります。

x の各値と assert の結果は以下のようになります。

x の値 assert の判定 備考
1 ok
1000 ok
1_000 ok 桁区切りリテラル
0b1111101000 ok 1000 の 2進数表記
01750 ok 1000 の 8進数表記
0x3E8 ok 1000 の 16進数表記
-1 failed
0 failed
0.9 failed float
1.0 failed float、範囲内だが型が違う
999.9 failed float、範囲内だが型が違う
1000.0 failed 範囲内だが型が違う
1000.1 failed float
1001 failed float
0b1111101001 failed 1001 の 2進数表記
01751 failed 1001 の 8進数表記
0x3E9 failed 1001 の 16進数表記

検証

実態に試してみます。

Playbook

以下の Playbook を利用します。要件1のときと似たような方式です。

ok_patternsfailed_patterns の内容が異なる点と、assertthat オプションに、型チェックの条件を加えてます。

- hosts: localhost
  gather_facts: no

  vars:
    ok_patterns:
      - 1
      - 1000
      - 1_000
      - 0b1111101000   # 1000
      - 01750          # 1000
      - 0x3E8          # 1000 
    failed_patterns:
      - -1
      - 0
      - 0.9
      - 1.0
      - 999.9
      - 1000.0
      - 1000.1
      - 1001
      - 0b1111101001   # 1001
      - 01751          # 1001
      - 0x3E9          # 1001

  tasks:
    - name: ok pattern
      assert:
        that: 
          - (item | type_debug) == "int"  # 型チェック
          - 1 <= item
          - item <= 1000
      loop: "{{ ok_patterns }}"
      
    - name: failed pattern
      assert:
        that:
          - (item | type_debug) == "int"  # 型チェック
          - 1 <= item
          - item <= 1000
      loop: "{{ failed_patterns }}"

実行結果

Playbook を実行します。

$ ansible-playbook -i localhost, validate/range2.yml 

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

TASK [ok pattern] *******************************************************************************************************
ok: [localhost] => (item=1) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}

TASK [failed pattern] ***************************************************************************************************
failed: [localhost] (item=-1) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": -1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1.0) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1.0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=999.9) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 999.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.0) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.1) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.1,
    "msg": "Assertion failed"
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}

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

1〜1000 の数値( int のみ ok)であることをチェックできました。


■ まとめ

数値の範囲を assert モジュールでチェックする方法を確認しました。

  • assert モジュールの that オプションに複数の条件を指定して、範囲を表現する
  • 型チェックも加える場合は、type_debug フィルターを利用する