以前、このブログで Fabric をスクリプトに組み込んで使う方法について書いた。
ただ、このやり方はちょっとした注意点があるので追記しておく。
今回使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G1108 $ python --version Python 3.5.2
インストール
何はともあれ、まずは Fabric をインストールしておく。 Python 3 に対応したものは fabric3 という名前でインストールできる。
$ pip install fabric3
Python 2 なら数字なし。
$ pip install fabric
下準備
前回のエントリと同じように Fabric で操作する対象は Vagrant で作った仮想マシンにする。 そのため、まずは Homebrew Cask を使って Vagrant と VirtualBox をインストールしておく。
$ xcode-select --install $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" $ brew tap caskroom/cask $ brew cask install vagrant virtualbox
次に、適当な作業ディレクトリに Vagrantfile を用意する。 これが操作する対象の仮想マシンの設定ファイルになる。
$ cat << 'EOF' > Vagrantfile # -*- mode: ruby -*- Vagrant.configure("2") do |config| config.vm.box = "bento/ubuntu-16.04" config.vm.provider "virtualbox" do |vb| vb.cpus = "2" vb.memory = "1024" end end EOF
用意ができたら、仮想マシンを起動しよう。
$ vagrant up
ssh-config サブコマンドで OpenSSH の設定を確認しておこう。 主に使うポート番号をチェックする。 今回は 2200 がアサインされた。 つまり、ポートフォワーディング経由で 127.0.0.1:2200 に SSH すると仮想マシンに接続できる。
$ vagrant ssh-config Host default HostName 127.0.0.1 User vagrant Port 2200 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/amedama/Documents/vagrant/fabric/.vagrant/machines/default/virtualbox/private_key IdentitiesOnly yes LogLevel FATAL
リモートホストでコマンドを実行する
以前のエントリと同じように Fabric を組み込んだスクリプトを用意する。 ここでは exit コマンドに引数 0 を指定して実行している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from fabric.api import run from fabric.api import execute from fabric.api import env def task(): run('exit 0') def main(): env.user = 'vagrant' env.password = 'vagrant' execute(task, hosts=['vagrant@127.0.0.1:2200']) print('Done!') if __name__ == '__main__': main()
Fabric のタスク実行が終わった後に Done! を出力しているところがポイント。
上記を fabfile.py というファイルで保存して、実行してみよう。
$ python fabfile.py [vagrant@127.0.0.1:2200] Executing task 'task' [vagrant@127.0.0.1:2200] run: exit 0 Done!
ちゃんと Done! の出力までされている。
問題があるパターン
次は同じソースコードでも、実行する exit コマンドの引数に 1 を渡してみよう。 こうすると exit コマンドの返り値が 1 になる。 返り値が非ゼロということは、エラー扱いになるということだ。
#!/usr/bin/env python # -*- coding: utf-8 -*- from fabric.api import run from fabric.api import execute from fabric.api import env def task(): run('exit 1') def main(): env.user = 'vagrant' env.password = 'vagrant' execute(task, hosts=['vagrant@127.0.0.1:2200']) print('Done!') if __name__ == '__main__': main()
さて、それでは上記を実行してみよう。
$ python fabfile.py [vagrant@127.0.0.1:2200] Executing task 'task' [vagrant@127.0.0.1:2200] run: exit 1 Fatal error: run() received nonzero return code 1 while executing! Requested: exit 1 Executed: /bin/bash -l -c "exit 1" Aborting.
なんと、今度は Done! の出力が見当たらない。
何が起こったのか?
実は Fabric の run() や sudo() といった関数は、実行したコマンドの返り値が非ゼロのときにプロセスを終了してしまう。 そのため、さきほどのサンプルコードでは非ゼロが帰った時点で Python のプロセスが終了してしまった。 これによって Done! を出力する行まで到達しなかったわけだ。
対処方法
この問題を回避するには、コマンドを実行する関数の warn_only という引数に True を指定すれば良い。
今度は、先ほどと同じソースコードで run() 関数の引数に warn_only=True を指定してみよう。
#!/usr/bin/env python # -*- coding: utf-8 -*- from fabric.api import run from fabric.api import execute from fabric.api import env def task(): run('exit 1', warn_only=True) def main(): env.user = 'vagrant' env.password = 'vagrant' execute(task, hosts=['vagrant@127.0.0.1:2200']) print('Done!') if __name__ == '__main__': main()
上記を実行してみよう。 今度は返り値が非ゼロであることについて警告は出るものの、そのまま処理が継続している。 そのため Done! が出力されていることも分かる。
$ python fabfile.py [vagrant@127.0.0.1:2200] Executing task 'task' [vagrant@127.0.0.1:2200] run: exit 1 Warning: run() received nonzero return code 1 while executing 'exit 1'! Done!
より細かいエラー処理
先ほどの例では、エラーがあっても突っ走ってしまうことを意味している。 このままだと、むしろ扱いにくいことになるだろう。 そこで、次はコマンドの実行結果が成功か失敗かを判断できるようにしよう。
コマンドの実行結果が成功か失敗か判断するには、コマンドを実行する関数の返り値を受け取るようにすれば良い。 次のようにして返り値の succeeded アトリビュートを確認する。 今回のサンプルコードではコマンドの実行が失敗したときには RuntimeError 例外を上げるようにしている。
#!/usr/bin/env python # -*- coding: utf-8 -*- from fabric.api import run from fabric.api import execute from fabric.api import env def task(): res = run('exit 1', warn_only=True, quiet=True) if not res.succeeded: raise RuntimeError('Oops!') def main(): env.user = 'vagrant' env.password = 'vagrant' try: execute(task, hosts=['vagrant@127.0.0.1:2200']) except RuntimeError: # ほんとはこんなエラーメッセージだしちゃダメダヨ print('Something wrong!') print('Done!') if __name__ == '__main__': main()
では、上記を実行してみよう。 今度はエラーをハンドリングしつつも Done! の表示が得られている。
$ python fabfile.py [vagrant@127.0.0.1:2200] Executing task 'task' Something wrong! Done!
かんぺきだね。 めでたしめでたし。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログを見る