【Ansible】対話型コマンドの自動化(expectモジュール)

# はじめに
こんにちは。
最近、Ansibleを使う業務に携わっており、自分でコードを作成する機会が増えてきました。
対話型コマンドの自動化でexpectモジュールを使ったので、紹介させてもらいます。
個人的な備忘録といった意味合いが強いですが、どなたかの参考になれば幸いです。

【注意】
本記事は、Ansibleの概念やplaybookの実行方法といった「Ansibleの基本」については紹介しておりませんのでご注意ください。

# 対話型コマンドとは？
コマンド実行後にさらにコマンド入力が求められるようなものを指します。色々ありますが、例えば下記のようなものです。
* sudoコマンド(コマンド実行後にパスワード入力を求められる。)
* sshコマンド(パスワード入力を求められる。)

# 環境準備
今回はWSLで実施します。詳細は下記です。
WSL:バージョン1
Ubuntu:22.04
ansible:2.14.1
python:3.10.6

# expectモジュールの紹介
expectモジュールのパラメータを紹介します。

|パラメータ  |内容  |選択肢(デフォルト値)  
|------|-----|-----|
|chdir   |コマンド実行前に移動するディレクトリ先。|-|
|command  |実行するコマンドを記載する。|-|
|creates  |ファイル作成。すでにファイルがあった場合はスキップする。|-|
|removes  |ファイル削除。すでにファイルがない場合はスキップする。|-|
|responses |応答の文字列を指定する。|-|
|echo |responsesで入力した値の出力可否。|yes,no(no)|
|timeout  |responsesで指定した文字列を探す時間。|-(30)|

# コード例
* 対話型のシェル(expect.sh)
対話型コマンドを実現するためのシェルです。
本来は「read -s password」にして、入力した値が表示されないようにするべきですが、今回は検証なので「-s」はつけてません。
```
#!/bin/bash

echo "名前を入れてください。:"
read username

echo "パスワード入力を入力してください。:"
read password

echo "あなたの名前は $username"
echo "あなたのパスワードは $password"
```

* プレイブック(expect.yaml)
```
- hosts: localhost
  become: yes
  gather_facts: no

  tasks:
    - name: expect_test
      expect:
        command: ./expect.sh
        responses:
          "名前を入れてください。": hoge
          "パスワードを入力してください。": test
      register: expect_result

    - name: debug_result
      debug:
        var: expect_result
```

# 実行結果
* 対話型のシェル(expect.sh)
```
root@ユーザ名:~# ./expect.sh
名前を入れてください。：
hoge
パスワードを入力してください。：
あなたの名前は hoge
あなたのパスワードは test
```

* プレイブック(expect.yaml)
```
root@ユーザ名:~# ansible-playbook expect.yaml
PLAY [localhost] ******************************************************************************************************************

TASK [expect_test] ****************************************************************************************************************
ok: [localhost]

TASK [debug_result] ***************************************************************************************************************
ok: [localhost] => {
    "expect_result": {
        "changed": false,
        "cmd": "./expect.sh",
        "delta": "0:00:00.275233",
        "end": "2023-07-06 13:50:15.719765",
        "failed": false,
        "rc": 0,
        "start": "2023-07-06 13:50:15.444532",
        "stdout": "名前を入れてください。:\r\nパスワードを入力してください。:\r\nあなたの名前は hoge\r\nあなたのパスワードは test",
        "stdout_lines": [
            "名前を入れてください。:",
            "パスワードを入力してください。:",
            "あなたの名前は hoge",
            "あなたのパスワードは test"
        ]
    }
}

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

```

ちなみに、expectモジュールのオプションである「echo: yes」にしてplaybookを実行してみると、対話型で入力した値が「stdout_lines」で表示されるようになります。

* プレイブック(expect.yaml)(echo: yesの場合)
```
root@ユーザ名:~# ansible-playbook expect.yaml
PLAY [localhost] ******************************************************************************************************************

TASK [expect_test] ****************************************************************************************************************
ok: [localhost]

TASK [debug_result] ***************************************************************************************************************
ok: [localhost] => {
    "expect_result": {
        "changed": false,
        "cmd": "./expect.sh",
        "delta": "0:00:00.275233",
        "end": "2023-07-06 13:55:15.719765",
        "failed": false,
        "rc": 0,
        "start": "2023-07-06 13:55:15.444532",
        "stdout": "名前を入れてください。:\r\nパスワードを入力してください。:\r\nあなたの名前は hoge\r\nあなたのパスワードは test",
        "stdout_lines": [
            "名前を入れてください。:",
            "hoge",
            "パスワードを入力してください。:",
            "test",
            "あなたの名前は hoge",
            "あなたのパスワードは test"
        ]
    }
}

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

# 最後に
いかがでしたでしょうか。対話型のコマンドは色々な種類があると思うのでexpectモジュールをうまく使って自動化してみましょう！
今後もAnsibleを使う機会があると思うので、他のモジュールも紹介できればと思います。

余談ですが、検証したWSL上にpexpectがインストールされていなくて、プレイブックを実行してもエラーになり、挫折しそうになりました。。。
実行環境については、ちゃんと公式のサイトに書いてありますね。。。公式ドキュメントはちゃんと読みます。(戒め)

# 参考
* Ansible公式ドキュメント expectモジュール

https://docs.ansible.com/ansible/2.9_ja/modules/expect_module.html?highlight=ansible%20doc