対話式のプログラムなんかを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って、動作はわかったけど、この機能の詳細、どうやってググればいいの……?