[小ネタ] ignore_errorsの罠

# 概要

なにかのモジュールでエラーが発生する際にそのエラーハンドリングとして`ignore_errors`を利用することがある（エラーを無視して、次の処理で戻り値を基に処理分岐するような実装）と思いますが、以下のようにモジュールの実行時エラーがスルーされる場合もあるので、追加の確認処理を設けるとより安全になるかと思います。

# コード

以下ではdebugモジュールのパラメータ`msg`を`msgs`にし、あえて実行時エラーを発生させます。
後続の`assert`は説明が簡単になるように簡単なアサーション条件になっています。

```yaml
- name: Sample
  hosts: localhost
  connection: local
  tasks:
    - ansible.builtin.debug:
        msgs: "test"
      ignore_errors: yes
      register: _result_debug

    - ansible.builtin.debug:
        msg="{{ _result_debug }}"

    - ansible.builtin.assert:
        that:
          - not _result_debug.changed
```

**実行ログ**

以下のように`failed=0`で処理が通過します。
後続処理の実装によっては問題ないが、サンプルコードのように実行時エラーを見抜けない場合があります。

```text
TASK [Gathering Facts] **********************************
Wednesday 08 December 2021  22:37:30 +0900 (0:00:00.027)       0:00:00.027 **** 
ok: [localhost]

TASK [ansible.builtin.debug] **********************************
Wednesday 08 December 2021  22:37:31 +0900 (0:00:00.521)       0:00:00.549 **** 
fatal: [localhost]: FAILED! => {"msg": "Invalid options for ansible.builtin.debug: msg2"}
...ignoring

TASK [debug] **********************************
Wednesday 08 December 2021  22:37:31 +0900 (0:00:00.020)       0:00:00.569 **** 
ok: [localhost] => {
    "msg": {
        "changed": false,
        "failed": true,
        "msg": "Invalid options for ansible.builtin.debug: msg2"
    }
}

TASK [ansible.builtin.assert] **********************************
Wednesday 08 December 2021  22:37:31 +0900 (0:00:00.031)       0:00:00.601 **** 
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}
PLAY RECAP **********************************
localhost: ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1 
```

## 実行時エラーを防ぐには

`ignore_errors`の直後に`not _result_debug.failed`を確認することで防ぐことができます。
ちなみに[`assert`モジュールの公式ドキュメント](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/assert_module.html)では`fail_msg`パラメータは文字列のみと書いていたが、実際は**配列**でも定義できます。また、必ず文字列に変換するように`string`フィルタも忘れずに。

```yaml
- name: Sample
  hosts: localhost
  connection: local
  tasks:
    - ansible.builtin.debug:
        msg2: "test"
      ignore_errors: yes
      register: _result_debug

    - debug:
        msg="{{ _result_debug }}"

    - ansible.builtin.assert:
        that:
          - not _result_debug.failed
      　fail_msg:
          - "{{ _result_debug | string }}"

    - ansible.builtin.assert:
        that:
          - not _result_debug.changed
```

**実行ログ**

追加した`assert`で実行時エラーをブロックしてくれてます。

```text
TASK [Gathering Facts] **********************************
Wednesday 08 December 2021  22:56:10 +0900 (0:00:00.027)       0:00:00.027 **** 
ok: [localhost]

TASK [ansible.builtin.debug] **********************************
Wednesday 08 December 2021  22:56:10 +0900 (0:00:00.512)       0:00:00.539 **** 
fatal: [localhost]: FAILED! => {"msg": "Invalid options for ansible.builtin.debug: msg2"}
...ignoring

TASK [debug] **********************************
Wednesday 08 December 2021  22:56:11 +0900 (0:00:00.020)       0:00:00.559 **** 
ok: [localhost] => {
    "msg": {
        "changed": false,
        "failed": true,
        "msg": "Invalid options for ansible.builtin.debug: msg2"
    }
}

TASK [ansible.builtin.assert] **********************************
Wednesday 08 December 2021  22:56:11 +0900 (0:00:00.030)       0:00:00.590 **** 
fatal: [localhost]: FAILED! => {
    "assertion": "not _result_debug.failed",
    "changed": false,
    "evaluated_to": false,
    "msg": [
        "{'failed': True, 'msg': 'Invalid options for ansible.builtin.debug: msg2', 'changed': False}"
    ]
}

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

ちなみに[公式ドキュメント](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_error_handling.html#ignoring-failed-commands)でも同じようなことが書かれています。

> 失敗したコマンドの無視
通常、Playbook はタスクが失敗したホストでこれ以上のステップの実行を停止します。 ただし、場合によっては、次に進みます。 これを行うには、以下のようなタスクを作成します。

> name: this will not be counted as a failure command: /bin/false ignore_errors: yes
上記のシステムは、特定のタスクの障害の戻り値のみを管理することに注意してください。 したがって、未定義の変数が使用されているか、構文エラーが発生した場合は、ユーザーがアドレスを指定することが必要になるエラーが出力されます。 接続または実行の問題の失敗を防ぐ訳ではないことに注意してください。 この機能は、タスクを実行でき、「faileded」の値を返す必要がある場合にのみ機能します