Ansible: shellモジュールでシェルのヒアドキュメントを使用する2つの方法

# 解消済み 2021/07追記

この問題はどこかのバージョンで対応が入って、少なくともansible 2.10.6では発生しない事を確認。
i.e. 行頭に勝手に空白が挿入されるということは無くなっている。




#問題

Ansibleのshellモジュールは、各行の先頭に空白文字を挿入して実行してしまう。その為、そのままシェルのヒアドキュメントを使用しようとすると、エラーとなったり意図しない動作となる可能性がある。

```yaml

- shell: |
    cat << EOC
    foo
    bar
    EOC
  register: result

- debug:
    msg: "{{ result }}"
```

出力

```
TASK [shell] *******************************************************************************************************************
changed: [ansiblepush-centos-7]

TASK [debug] *******************************************************************************************************************
ok: [ansiblepush-centos-7] => {
    "msg": {
        "changed": true, 
        "cmd": "cat << 'EOC'\n foo\n bar\n EOC", 
...
        "failed": false, 
        "rc": 0, 
...
        "stderr_lines": [
            "/bin/sh: line 3: warning: here-document at line 0 delimited by end-of-file (wanted `EOC')"
        ], 
...
        "stdout_lines": [
            " foo", 
            " bar", 
            " EOC"
        ]
    }
}
```

ヒアドキュメントの区切り文字が見つからないとワーニングを出しつつも、このケースではタチ悪くエラーとならず、空白付き区切り文字を含め、スクリプトの最後までを出力してしまっている。後半に前半と対となるキーワードがあればエラーとなる。
result.cmdより、最後の区切り文字列を含め、各行とも先頭に空白文字が挿入され、区切り文字が見つかっていないことが判る。
タブ文字ではない為、<<-によっても対応できない。


#対応方法1
shellモジュールのcmdパラメーターとして与えることで、先頭に空白が入ることを防ぎ、ヒアドキュメントを正常に動作させる事が可能。

```yaml

- shell:
    cmd: |
      cat << EOC
      foo
      bar
      EOC
  register: result

- debug:
    msg: "{{ result.stdout }}"
```


```
TASK [shell] *******************************************************************************************************************
changed: [ansiblepush-centos-7]

TASK [debug] *******************************************************************************************************************
ok: [ansiblepush-centos-7] => {
    "msg": {
        "changed": true, 
        "cmd": "cat << 'EOC'\nfoo\nbar\nEOC\n", 
...
        "failed": false, 
        "rc": 0, 
...
        "stderr_lines": [], 
...
        "stdout_lines": [
            "foo", 
            "bar"
        ]
    }
}
```

参考：

- [How to do multiline shell script in Ansible : 130](
https://stackoverflow.com/questions/40230184/how-to-do-multiline-shell-script-in-ansible)
- [Ansibleのshellモジュールでヒアドキュメントを使うための引数cmdをソースから追ってみた](https://qiita.com/dkuji/items/7bbbea2ad65614c57705)

#対応方法2

<< ' EOC'の様に、指定する区切り文字列の先頭を空白文字とし、かつ終端で実際に指定する区切り文字列はその空白を除いた値とする。
この場合、ヒアドキュメント中では変数展開などは無効化されることに注意。(少なくともRubyではダブルクォートで囲った場合には展開を有効化、シングルクォートで囲った場合には無効化と、使い分け可能なのだが、シェルではどちらでも無効化の様子。) 
ヒアドキュメントとしてはこれで動作できる様になるが、ヒアドキュメント中の先頭空白は入ったまま。それらも除きたければ例えば別途sedなどを用いる。

```yaml

- shell: |
    cat << ' EOC'
      foo
      bar
      EOC
  register: result

# ヒアドキュメント中の先頭空白も除くなら、cat の行を sed 's/^ //' << ' EOC' などとする

- debug:
    msg: "{{ result }}"
```

```
TASK [shell] *******************************************************************************************************************
changed: [ansiblepush-centos-7]

TASK [debug] *******************************************************************************************************************
ok: [ansiblepush-centos-7] => {
    "msg": {
        "changed": true, 
        "cmd": "cat << ' EOC'\n foo\n bar\n EOC", 
...
...
        "failed": false, 
        "rc": 0, 
...
        "stderr_lines": [], 
...
        "stdout_lines": [
            " foo", 
            " bar"
        ]
    }
}
```




参考：

- [ansible 1.7 passing an argument with newlines to the shell module eats newlines (edited title) #8512](https://github.com/ansible/ansible/issues/8512#issuecomment-102374752)
- [入力と出力 | UNIX & Linux コマンド・シェルスクリプト リファレンス : ヒアドキュメント](https://shellscript.sunone.me/input_output.html#ヒアドキュメント)

#比較
制約の無い対応方法1で良いでしょう。

#回避方法
代わりにscript moduleを使用する。

参考：

- [shell - Execute commands in nodes. : Notes](https://docs.ansible.com/ansible/2.5/modules/shell_module.html)