対話式のプログラムなんかをAnsibleで実行したい場合はexpectモジュールを使用する。
なお、以下のライブラリが追加で必要
- pexpect >= 3.3
システムにインストールされていないとエラーになる。
※ インストールが必要なのはコントロールノードでなく、ターゲットノード。もちろんPlaybookでインストールするtaskを書いてよい。
TASK [expect] ****************************************************************** An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ImportError: No module named pexpect fatal: [localhost]: FAILED! => changed=false msg: Failed to import the required Python library (pexpect) on control-node's Python /usr/bin/python2. Please read module documentation and install in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter
入っていてもバージョンが古いとやはりエラーになる。(yum
でインストールできるCentOSのPython2用のpexpectだと2.3)
TASK [expect] ****************************************************************** fatal: [localhost]: FAILED! => changed=false msg: Insufficient version of pexpect installed (2.3), this module requires pexpect>=3.3. Error was 'module' object has no attribute 'runu'
なので、インストールするにはpip
を使うかPython3版をyum installする。
- Python3で
yum install
- Python3で
pip install
- Python2で
pip install
whlファイルをpip2
であればこんな感じ
$ sudo pip2 install ptyprocess-0.6.0-py2.py3-none-any.whl $ sudo pip2 install pexpect-4.7.0-py2.py3-none-any.whl
yumでPython3のライブラリインストール
$ yum info python36-pexpect
Python2なAnsibleでPython3のライブラリを使う場合は以下も参照。
実行例
プロンプト毎に値を入力する
サンプルスクリプト
#!/usr/bin/perl use strict; print "Username: "; my $username = <>; print "Password: "; my $password = <>; chomp($username, $password); print "----\n"; print "input uesrname: $username\n"; print "input password: $password\n";
こんなスクリプトを用意。
実行例
[zaki@control-node expect]$ ./interactive Username: zaki Password: curry_tabetai ---- input uesrname: zaki input password: curry_tabetai [zaki@control-node expect]$
playbook
--- - hosts: localhost gather_facts: False tasks: - expect: command: /home/zaki/ansible/expect/interactive responses: "Username: " : "zaki" "Password: " : "curry_tabetai"
ansible-playbook実行
[zaki@control-node expect]$ ansible-playbook -i inventory.ini playbook.yml -v Using /home/zaki/ansible/expect/ansible.cfg as config file PLAY [localhost] *************************************************************** TASK [expect] ****************************************************************** changed: [localhost] => changed=true cmd: /home/zaki/ansible/expect/interactive delta: '0:00:00.206796' end: '2020-01-15 21:05:56.902099' rc: 0 start: '2020-01-15 21:05:56.695303' stdout: |- Username: Password: ---- input uesrname: zaki input password: curry_tabetai stdout_lines: <omitted>
ちゃんとプロンプトに応じて入力が行われてる。
サンプルはlocalhost
に対して処理してるけど、もちろんリモートでも実行できる。
その際、pexpect Pythonライブラリはexpect
処理を行うターゲットノード上に必要 。
同じプロンプトに対して順番に違う値を入力する
平たく言うとシェルのように毎回同じプロンプトみたいなやつ。
playbook
やってる内容は以下の2つの処理
ssh remote.example.org command
で、ssh
の引数にコマンドも与えて、リモートホスト上のcommand
コマンドを実行ssh remote.example.org
でsshシェルログインしてから、(expect
モジュール経由で)alias
とexit
を実行
--- - hosts: nodes gather_facts: False tasks: - command: alias - hosts: localhost gather_facts: False tasks: - shell: ssh 192.168.0.141 alias - hosts: localhost gather_facts: False tasks: - expect: command: ssh 192.168.0.141 responses: "\\]\\$ ": - "alias" - "exit"
リモートホスト上のプロンプトは以下のように、[username@hostname dir]$
になっているので]$
部分を正規表現で指定している。
[zaki@control-node expect]$ ssh 192.168.0.141 Last login: Wed Jan 15 21:21:53 2020 from 192.168.0.140 [zaki@target-node01 ~]$ hostname target-node01 [zaki@target-node01 ~]$
実行結果
[zaki@control-node expect]$ ansible-playbook -i inventory.ini playbook.yml -v Using /home/zaki/ansible/expect/ansible.cfg as config file PLAY [localhost] *************************************************************** TASK [shell] ******************************************************************* changed: [localhost] => changed=true cmd: ssh 192.168.0.141 alias delta: '0:00:00.259279' end: '2020-01-15 21:05:57.277030' rc: 0 start: '2020-01-15 21:05:57.017751' stderr: '' stderr_lines: <omitted> stdout: |- alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' stdout_lines: <omitted> PLAY [localhost] *************************************************************** TASK [expect] ****************************************************************** changed: [localhost] => changed=true cmd: ssh 192.168.0.141 delta: '0:00:00.475647' end: '2020-01-15 21:05:57.879156' rc: 0 start: '2020-01-15 21:05:57.403509' stdout: |- Last login: Wed Jan 15 21:05:56 2020 from 192.168.0.140 [zaki@target-node01 ~]$ alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' [zaki@target-node01 ~]$ Connection to 192.168.0.141 closed. stdout_lines: <omitted>
ちゃんと動いてる。
shell
を使ってssh remote.example.org alias
については特筆することはないので見た通り。
expect
の方は"\\]\\$ "
に対してalias
とexit
を順番に実行できている。(alias
の結果とexit
の結果が出力されてる)
余談 ... aliasをぶっこ抜く
なんでこんなことやってるかとゆーと、仕事で「対象全ホストの設定情報ぜんぶ抜く」という作業があり、環境変数やらaliasやらをcommand: alias
などで雑に集めたら「ssh
で手作業で確認したaliasの内容と異なるんだが!?」という指摘され、確認したら確かに差異(↑の出力もcolorオプションの部分が異なる)があってなんでやろーと調べて行ったところ、、、
/etc/profile.d/colorls.sh
で定義されてる、ls
の色設定のエイリアスに関するスクリプト、冒頭に
# Skip all for noninteractive shells. [ ! -t 0 ] && return
という処理があり、シェルログインみたいにインタラクティブな状態じゃない時には設定されないという定義を発見。
そのため、ssh remote.example.org alias
とやっても、このコードでreturn
され残りのalias
設定がされないという。。 (CentOS7、RHEL7で確認)
ちなみにこれ、シェル上からssh -t remote.example.org alias
とやれば、期待通りの結果を得られる。
じゃあPlaybookのshell
モジュール(またはcommand
)に、ssh -t ...
ってやればいいじゃん?と思うんだけど、これはこれで「端末からの実行じゃないのでダメ」と言われる。
TASK [shell] ******************************************************************* changed: [localhost] => changed=true cmd: ssh -t 192.168.0.141 alias delta: '0:00:00.283072' end: '2020-01-15 21:44:20.438807' rc: 0 start: '2020-01-15 21:44:20.155735' stderr: Pseudo-terminal will not be allocated because stdin is not a terminal. stderr_lines: <omitted> stdout: |- alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' stdout_lines: <omitted>
で、shell
モジュールでalias
の結果を引っこ抜くのは諦めてたんだけど、ググってみたところ「-t
オプションを多重に付与すれば強制的にtty割り当てられる」という情報(というかman
だけど)を発見
-t Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty.
- hosts: localhost gather_facts: False tasks: - shell: ssh -tt 192.168.0.141 alias
実行結果
TASK [shell] ******************************************************************* changed: [localhost] => changed=true cmd: ssh -tt 192.168.0.141 alias delta: '0:00:00.277060' end: '2020-01-15 21:52:52.725024' rc: 0 start: '2020-01-15 21:52:52.447964' stderr: Connection to 192.168.0.141 closed. stderr_lines: <omitted> stdout: |- alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' stdout_lines: <omitted>
expect使わなくても行けるやん。。
alias設定スクリプトの! -t 0
って、動作はわかったけど、この機能の詳細、どうやってググればいいの……?