Ansibleでループの条件分岐したい時

# 目的
現在インストール済みのパッケージを確認し、有無によって実行処理を分けたい場合があると思うので、それをやってみたいと思います
今回はasdfのプラグインの場合で確認しますが、それ以外のパッケージマネージャーでも利用できます
もちろんモジュールがあるならそれを使ったほうがいいですが、モジュールを作ろうとすると、結構よく出てくる問題です

# 対象環境

```bash
> ansible --version
ansible 2.7.1
  config file = None
  configured module search path = ['/Users/loveansibleman/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/2.7.1/libexec/lib/python3.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.7.1 (default, Nov 11 2018, 22:35:12) [Clang 10.0.0 (clang-1000.11.45.5)]
```

# 方法
## 変数代入
まず、変数代入の部分ですが、基本的にwith_itemsと同じ部分に記載すれば良いです
今回はasdf pluginを確認して、条件分岐をしたいので、`asdf plugin-list`の結果をgrepで必要な言語をwith_items配下で指定してgrepしています
asdfの場合、`.bash_profile`を読み込んで実行する必要があるので、先頭に`/bin/bash -lc`をつけています
rbenvなどをインストールする時も同じようなことがありますが、必要に応じてつけてください

```yaml
- name: Get installed plugin list
  shell: /bin/bash -lc "asdf plugin-list | grep {{ item }}"
  register: result
  with_items:
    - python
    - ruby
    - golang
    - java
  ignore_errors: yes
  changed_when: no
```

grepは引っかからなければエラーとなるので`ignore_errors: yes`とし、コマンドが実行出来ればchangeとなるので`changed_when: no`を入れています

結果が下記です

```json
TASK [Get installed plugin list] ***************************************************************
ok: [127.0.0.1] => (item=python)
ok: [127.0.0.1] => (item=ruby)
ok: [127.0.0.1] => (item=golang)
failed: [127.0.0.1] (item=java) => {"changed": false, "cmd": "/bin/bash -lc \"asdf plugin-list | grep java\"", "delta": "0:00:00.574073", "end": "2019-01-26 10:42:38.981170", "item": "java", "msg": "non-zero return code", "rc": 1, "start": "2019-01-26 10:42:38.407097", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring
```

python, ruby, golangは存在するのでgrepでエラーにならず、また`changed_when: no`を入れているためchangeにもならず、okになっています。
javaは存在しないためエラーになっていますが、`ignore_errors: yes`を入れているため、ignoringとなっています

## 変数の中身

resultの中身をみたいので、下記Playbookを実行します

```yaml
- name: View result
  debug: 
    var: result
```

すると結果がこちらです

```json
TASK [View result] *****************************************************************************
ok: [127.0.0.1] => {
    "result": {
        "changed": false,
        "failed": true,
        "msg": "All items completed",
        "results": [
            {
                "_ansible_ignore_errors": true,
                "_ansible_item_label": "python",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_parsed": true,
                "changed": false,
                "cmd": "/bin/bash -lc \"asdf plugin-list | grep python\"",
                "delta": "0:00:00.694997",
                "end": "2019-01-26 10:42:36.372877",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "/bin/bash -lc \"asdf plugin-list | grep python\"",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "warn": true
                    }
                },
                "item": "python",
                "rc": 0,
                "start": "2019-01-26 10:42:35.677880",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "python",
                "stdout_lines": [
                    "python"
                ]
            },
            {
                "_ansible_ignore_errors": true,
                "_ansible_item_label": "ruby",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_parsed": true,
                "changed": false,
                "cmd": "/bin/bash -lc \"asdf plugin-list | grep ruby\"",
                "delta": "0:00:00.584345",
                "end": "2019-01-26 10:42:37.258385",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "/bin/bash -lc \"asdf plugin-list | grep ruby\"",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "warn": true
                    }
                },
                "item": "ruby",
                "rc": 0,
                "start": "2019-01-26 10:42:36.674040",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "ruby",
                "stdout_lines": [
                    "ruby"
                ]
            },
            {
                "_ansible_ignore_errors": true,
                "_ansible_item_label": "golang",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_parsed": true,
                "changed": false,
                "cmd": "/bin/bash -lc \"asdf plugin-list | grep golang\"",
                "delta": "0:00:00.579275",
                "end": "2019-01-26 10:42:38.121094",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "/bin/bash -lc \"asdf plugin-list | grep golang\"",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "warn": true
                    }
                },
                "item": "golang",
                "rc": 0,
                "start": "2019-01-26 10:42:37.541819",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "golang",
                "stdout_lines": [
                    "golang"
                ]
            },
            {
                "_ansible_item_label": "java",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_parsed": true,
                "changed": false,
                "cmd": "/bin/bash -lc \"asdf plugin-list | grep java\"",
                "delta": "0:00:00.574073",
                "end": "2019-01-26 10:42:38.981170",
                "failed": true,
                "invocation": {
                    "module_args": {
                        "_raw_params": "/bin/bash -lc \"asdf plugin-list | grep java\"",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "warn": true
                    }
                },
                "item": "java",
                "msg": "non-zero return code",
                "rc": 1,
                "start": "2019-01-26 10:42:38.407097",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "",
                "stdout_lines": []
            }
        ]
    }
}
```

これを見ると、`result.results`の中にwith_itemsのそれぞれの結果が配列として入っています
その配列の中身の中にfailed, item, cmdなどが入っています
それぞれの説明は下記の通りです

| key名 | valueの説明 |
| -------- | -------- | 
| failed     | 失敗したかどうかのboolean値     | 
| item     | with_itemsで指定した値     | 
| cmd     | 今回実行したコマンド     | 

つまり、`result.results`でループを回し、その中の`failed`で条件分岐が出来ることがわかりました
また、`item`, `cmd`も必要であればループ内で利用できます

## 条件分岐

上記の結果を元に、result.resultsをwith_itemsにいれ、whenに`not item.failed`を入れると、存在した場合の条件分岐ができ、`item.failed`を入れると存在しない場合の条件分岐ができます
その時の設定が下記です

```yaml
- name: If listed items
  debug: 
    msg: "{{ item.item }} is found"
  with_items: '{{ result.results }}'
  when: not item.failed

- name: If not listed items
  debug: 
    msg: "{{ item.item }} is not found"
  with_items: '{{ result.results }}'
  when: item.failed
```

結果が下記です

```json
TASK [If listed items] *************************************************************************
ok: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:36.372877', 'stdout': 'python', 'cmd': '/bin/bash -lc "asdf plugin-list | grep python"', 'rc': 0, 'start': '2019-01-26 10:42:35.677880', 'stderr': '', 'delta': '0:00:00.694997', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep python"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['python'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'python', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'python'}) => {
    "msg": "python is found"
}
ok: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:37.258385', 'stdout': 'ruby', 'cmd': '/bin/bash -lc "asdf plugin-list | grep ruby"', 'rc': 0, 'start': '2019-01-26 10:42:36.674040', 'stderr': '', 'delta': '0:00:00.584345', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep ruby"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['ruby'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'ruby', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'ruby'}) => {
    "msg": "ruby is found"
}
ok: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:38.121094', 'stdout': 'golang', 'cmd': '/bin/bash -lc "asdf plugin-list | grep golang"', 'rc': 0, 'start': '2019-01-26 10:42:37.541819', 'stderr': '', 'delta': '0:00:00.579275', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep golang"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['golang'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'golang', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'golang'}) => {
    "msg": "golang is found"
}
skipping: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:38.981170', 'stdout': '', 'cmd': '/bin/bash -lc "asdf plugin-list | grep java"', 'failed': True, 'delta': '0:00:00.574073', 'stderr': '', 'rc': 1, 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep java"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, 'start': '2019-01-26 10:42:38.407097', 'msg': 'non-zero return code', '_ansible_parsed': True, 'stdout_lines': [], 'stderr_lines': [], '_ansible_no_log': False, 'item': 'java', '_ansible_item_result': True, '_ansible_item_label': 'java'})

TASK [If not listed items] ********************************************************************
skipping: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:36.372877', 'stdout': 'python', 'cmd': '/bin/bash -lc "asdf plugin-list | grep python"', 'rc': 0, 'start': '2019-01-26 10:42:35.677880', 'stderr': '', 'delta': '0:00:00.694997', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep python"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['python'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'python', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'python'})
skipping: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:37.258385', 'stdout': 'ruby', 'cmd': '/bin/bash -lc "asdf plugin-list | grep ruby"', 'rc': 0, 'start': '2019-01-26 10:42:36.674040', 'stderr': '', 'delta': '0:00:00.584345', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep ruby"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['ruby'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'ruby', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'ruby'})
skipping: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:38.121094', 'stdout': 'golang', 'cmd': '/bin/bash -lc "asdf plugin-list | grep golang"', 'rc': 0, 'start': '2019-01-26 10:42:37.541819', 'stderr': '', 'delta': '0:00:00.579275', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep golang"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, '_ansible_parsed': True, 'stdout_lines': ['golang'], 'stderr_lines': [], '_ansible_no_log': False, 'failed': False, 'item': 'golang', '_ansible_item_result': True, '_ansible_ignore_errors': True, '_ansible_item_label': 'golang'})
ok: [127.0.0.1] => (item={'changed': False, 'end': '2019-01-26 10:42:38.981170', 'stdout': '', 'cmd': '/bin/bash -lc "asdf plugin-list | grep java"', 'failed': True, 'delta': '0:00:00.574073', 'stderr': '', 'rc': 1, 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, '_raw_params': '/bin/bash -lc "asdf plugin-list | grep java"', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin': None}}, 'start': '2019-01-26 10:42:38.407097', 'msg': 'non-zero return code', '_ansible_parsed': True, 'stdout_lines': [], 'stderr_lines': [], '_ansible_no_log': False, 'item': 'java', '_ansible_item_result': True, '_ansible_item_label': 'java'}) => {
    "msg": "java is not found"
}
```

python, ruby, golangだと１個目のplaybookで実行され、javaだと１個目はスキップし２個目で実行されていることがわかります
やったね！

# まとめ
今回利用したplaybookは下記になりますので、ご利用ください

```yaml
- name: Get installed plugin list
  shell: /bin/bash -lc "asdf plugin-list | grep {{ item }}"
  register: result
  with_items:
    - python
    - ruby
    - golang
    - java
  ignore_errors: yes
  changed_when: no

- name: View result
  debug: 
    var: result

- name: If listed items
  debug: 
    msg: "{{ item.item }} is found"
  with_items: '{{ result.results }}'
  when: not item.failed

- name: If not listed items
  debug: 
    msg: "{{ item.item }} is not found"
  with_items: '{{ result.results }}'
  when: item.failed
```

# 参考
* [Ansible - using with_items and when conditional to - Stack Overflow](https://stackoverflow.com/questions/32214529/ansible-using-with-items-and-when-conditional-to)
* [Ansible shell module returns error when grep results are empty - Stack Overflow](https://stackoverflow.com/questions/41010378/ansible-shell-module-returns-error-when-grep-results-are-empty)