Ansibleのdebugモジュールを使ってログを出力する方法がshellモジュールを使ったかどうかで違ったのでまとめる

Ansibleのdebugモジュールを使う際、shellモジュールを使っているかどうかで出力方法がやや異なります。

では、どのように違うのか？

その違いを忘れないように下記のような表を作りました。

#モジュール別のdebug方法一覧表

|                           |標準出力のdebug              |エラー出力のdebug                 |
|---------------------------|---------------------------|--------------------------------|
|shellモジュール             | "{{ $RESULT.stdout }}"     | "{{ $RESULT.stderr }}"         |
|shellモジュール以外(fileなど)| "{{ $RESULT }}"            | "{{ $RESULT }}"                 |


上の表を作るにあたり、えいやっで作ったyamlはこちらです。


##検証するために作ったsample.yml

```yaml
---
- hosts: 
    - dev
  become: yes
  vars:
    ##成功、stdoutのみ"dir_test "と出力あり。
    #target: /usr/etc/
　　 ##成功、stdout/stderrともに出力なし。
    #target: /usr/etc/dir_test 
    ##失敗、stderのみ"No such file or directory"と出力あり。
    target: /usr/ett  

  tasks:
    #- name: Check if target dir exists or not
    - stat:
        path: "{{ target }}"
      register: result_stat
      failed_when: false 
      ##moduleの実行結果を確認する場合は"{{ $REGISTER }}"
    - name: debug 
      debug:
        msg: "{{ result_stat }}"
    - shell: ls "{{ target }}"
      register: result_shell
      failed_when: false
      changed_when: false
　　  #shellモジュールで直接コマンドを叩いた実行結果を確認する場合は"{{ $REGISTER.stdout or $REGISTER.stderr }}"
    - name: debug result_shell.stdout
      debug:
        msg: "{{ result_shell.stdout }}"
    - name: debug result_shell.stderr
      debug:
        msg: "{{ result_shell.stderr }}"

```

上の表を作るにあたり、ドキュメントを読み漁っていくなかでshellモジュールの箇所とそのソースコードが参考になったので、
併せて紹介させてください。

##debug方法を理解するためにshellモジュールのドキュメントとソースコードを少し読んで分かったこと
- ``内部的にsubprocessモジュールを使い、`` run_commandメソッドとしてコマンドを叩いていた
- 返却値は``rc(リターンコード), stdout(標準出力), stderr(エラー出力)の3つ``

つまり、debugモジュールでログを吐き出す方法がshellモジュールを使っているかどうかで違うというのは、subprocess.Popen()を使っているかどうかと読み替えてもいいかもしれません。


>run_command(args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None, environ_update=None, umask=None, encoding='utf-8', errors='surrogate_or_strict', expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None)
**Execute a command, returns rc, stdout, and stderr.**

>Parameters:	args – is the command to run * If args is a list, the command will be run with shell=False. * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False * If args is a string and use_unsafe_shell=True it runs with shell=True.
Kw check_rc:	Whether to call fail_json in case of non zero RC. Default False
Kw close_fds:	See documentation for subprocess.Popen(). Default True
Kw executable:	See documentation for subprocess.Popen(). Default None

出所：[Ansible Reference: Module Utilities](https://docs.ansible.com/ansible/latest/reference_appendices/module_utils.html)

cf. subprocessモジュールでコマンドを叩いているところ
[ansible/basic.py at devel · ansible/ansible](https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/basic.py)


subprocessモジュールの返却値はstdin(標準入力), stdout, stderrの3つですが、run_commandメソッドではstdinではなく、rcが返却されています。
ついでにrc, リターンコードについてかるーく調べたので、備忘録も兼ねて本記事に続けて書いていきます。

#Ansibleにおけるリターンコード
`Ansibleにおけるリターンコード` と書いたのですが、そもそもリターンコードについて造詣が深いわけではないので、下記のシェルスクリプトに関する記事からリターンコードの説明箇所を引用します。

>正常終了時は exit 0 で、異常終了時には exit 1 で終了するようにするのが慣例

出所：[終了ステータス | UNIX &amp; Linux コマンド・シェルスクリプト リファレンス]
(https://shellscript.sunone.me/exit_status.html)

そんなリターンコードですが、``Ansibleにおけるリターンコードは一般的なそれとはやや異なる``ようです。
公式docsにリターンコード(rc)について興味深い記述が合ったので共有します。

>Ansible normally has defaults that make sure to check the return codes of commands and modules and it fails fast – forcing an error to be dealt with unless you decide otherwise.

>Sometimes a command that `returns different than 0 isn’t an error`. Sometimes a command might `not always need to report that it ‘changed’` the remote system. This section describes how to change the default behavior of Ansible for certain tasks so output and error handling behavior is as desired.

[@gkzvoice](https://twitter.com/gkzvoice) 超意訳
(Ansibleの)利用者が通常コマンド及びモジュールのリターンコードを確認するようにし、また特に断らない限り、強制的にエラーを処理するために、早くfailedと出力するようにしている。

`0以外のリターンコードが返却されたコマンドはエラーと判定しない`ケースがある。リモート機器(Ansibleにとってのターゲット機器)が`'changed'と通知する必要が必ずしもない`。本セクションでは、Ansibleの通常のふるまい(一般的なリターンコードと同様0は正常終了、1は異常終了と判定するロジック)をどのように変更して、Ansibleに求められているタスクの実行結果の出力とエラー判定方法を(Ansibleの利用者が)おこなっていけばよいか、(サンプルコードも交えて)取り上げていく。


キッチリ正常判定を行う場合と条件が複数行にまたがる場合にスマートに書く場合のサンプルコードを引用して本記事を終えたいと思います。

###キッチリ正常判定を行う場合
```yaml
failed_when: result.rc == 0 or "No such" not in result.stdout
```

###条件が複数行にまたがる場合にスマートに書く場合
```yaml

- name: example of many failed_when conditions with OR
  shell: "./myBinary"
  register: ret
  failed_when: >                                        #":"の後に">"を書けば改行できるようですね。
    ("No such file or directory" in ret.stdout) or
    (ret.stderr != '') or
    (ret.rc == 10)
```
出所： [Error Handling In Playbooks &mdash; Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html)


P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます！
[@gkzvoice](https://twitter.com/gkzvoice)