リモートプログラムとのやりとり

Fabricの主な操作、 runsudo では、ある意味 ssh とほとんど同じようにローカルの入力をリモート側に送ることができます。例えば、パスワードのプロンプトを表示するプログラム(例えば、データベースのダンプユーティリティやユーザーパスワードの変更など)は直接やり取りをしているように振る舞います。

とは言え、 ssh と同様に、Fabricのこの機能の実装も直感的とは限らないいくつかの制限に影響されます。このドキュメントではこうした問題について詳述します。

注釈

Unixの標準出力や標準エラー出力のパイプとターミナルデバイスに基礎に馴染みのない読者は Unix パイプ擬似端末 を参照するとよいでしょう。

標準出力と標準エラー出力の結合

最初に気づく問題は標準出力と標準エラー出力のストリームで、なぜ両者が随時分離したり結合したりするのかということです。

バッファリング

Fabric 0.9.x以前では、またPython自身でも、バッファーの出力は原則的に行ごとで、新しい文字が現れるまでテキストはユーザーに表示されませんでした。これは大抵の場合は問題ありませんが、プロンプトのように一部分の行の出力を扱う必要がある場合には問題となります。

注釈

行でバッファされた出力はプログラムを理由なく中断させたりフリーズさせたりするように見えます。プロンプトは新しい行なしでテキストを表示し、ユーザーが入力してリターンを押すのを待ちます。

新しいFabricのバージョンは入力と出力の両方ともを文字ごとにバッファし、プロンプトとのやりとりを可能にします。これは、 "呪われた" ライブラリを活用する複雑なプログラムとのやりとりを可能にします。さもなければスクリーンを再描画します( top を考えてみてください)。

ストリームの交差

残念ながら、標準エラー出力と標準出力へ同時に表示させるということは(多くのプログラムで行われていますが)、この2つのストリームが無関係に1バイトずつ表示されるという意味であり、文字化けしたり、混ざって表示されたりすることがあります。これはストリームの一方を行でバッファすることで緩和させることはできますが、重要な問題であることには変わりありません。

この問題を解決するため、Fabricでは低レベルでこの2つをマージするSSH層の設定を利用し、出力をより自然に表示するようにしています。この設定はFabricでは combine_stderr env変数とキーワード引数として表され、デフォルトでは``True`` が設定されています。

このデフォルトの設定のおかげで出力が正しく表示されますが、run/sudo の返り値に空の .stderr 属性という代償を払っていて、すべての出力が標準出力に表示されてしまいます。

逆に、Pythonレベルでの標準エラー出力のストリームを区別した形で必要とするユーザーやユーザーが目にする出力が文字化けしていても構わないユーザー(あるいは、問題となる標準出力や標準エラー出力をコマンドから隠しているユーザー)は、必要に応じてこの値を False にセットすることもできます。

擬似ターミナル

ユーザーに対話式プロンプトを表示するときに考慮すべきもう一つの大きな問題は、ユーザー自身の入力のエコーです。

エコー

一般的なターミナルアプリケーションや本物のテキストターミナル(例えば、GUIなしでUnixシステムを使用している時)ではttyもしくはpty (pseudo-terminal - 擬似ターミナル)と呼ばれるターミナルデバイスとともにプログラムが表示されます。タイプした文字を見ずにやりとりするのは困難なため、これらでは、タイプされたすべてのテキストが(標準出力経由で)ユーザーに自動的にエコーされます。ターミナルデバイスでもまた、条件付きでエコーを向こうにでき、パスワードのプロンプトを安全にできます。

とは言え、ttyやptyをまったく表示させなくてもプログラムは実行可能(例えばcronのジョブを考えてみてください)で、このプログラムに注ぎ込まれるどんな標準入力のデータもエコーされません。これは人間が周りにいなくても実行されるプログラムにとっては望ましい挙動で、Fabricの以前のデフォルトの操作モードでした。

Fabricのアプローチ

残念ながら、Fabric経由でコマンドを実行するということは、ユーザーの標準入力をエコーするためのptyがないときにFabricがそれをエコーしなくてはならないということです。これは多くのアプリケーションでは問題ありませんが、パスワードプロンプトにとっては問題であり、安全ではありません。

セキュリティ上の理由と驚かすことは最小にするべきという原則に沿うため(ユーザーが一般的にはターミナルエミュレータで動作しているときと同じような挙動を期待している限りにおいては)、Fabric 1.0以上ではデフォルトでptyを強制します。ptyが有効のとき、Fabricは単にリモート側でエコーを扱うようにするか、もしくは標準入力を隠してまったく何もエコーしないようにします。

注釈

通常のエコーの挙動を許可することに加えて、ターミナルデバイスにアタッチされた時に別の挙動を示すプログラムは、その挙動を示します。例えば、ターミナル上ではカラーで出力し、バックグラウンドではカラーで出力しないプログラムは、ptyはカラーで出力します。run もしくは sudo の返り値を調べる場合は注意してください!

ptyの挙動の無効が求められる場合は、 --no-pty のコマンドライン引数と always_use_pty env変数が利用可能です。

この2つの統合

最後の注意点として、擬似ターミナルの利用は事実上、標準出力と標準エラー出力を統合するということです。これは combine_stderr の設定が行うのとまったく同じ方法です。これはターミナルデバイスが当然ながら標準出力と標準エラー出力の両方を同じ場所、ユーザーのディスプレイに送るためで、したがってこれにより両者の分化が不可能になっています

とは言え、Fabricレベルではこの2つの設定グループはそれぞれ明確に区別され、様々な方法で統合することができます。デフォルトでは、両方とも True にセットされています。他の組み合わせは以下になります。

  • run("cmd", pty=False, combine_stderr=True): Fabricはパスワードも含め、すべての標準入力をエコーします。また、 cmd の挙動が変更される可能性もあります。ptyで動作していてパスワードのプロンプトについて心配していない時に cmd の挙動が好ましくない場合に便利です。
  • run("cmd", pty=False, combine_stderr=False): 両方の設定が False の時、Fabricは標準入力をエコーし、ptyを発行しません。そしてこれは、もっともシンプルなコマンド以外のすべてで好ましくない挙動をもたらす可能性が高くなります。とは言え、区別された標準エラー出力にアクセスする唯一の方法ですし、そうした場合には便利です。
  • run("cmd", pty=True, combine_stderr=False): 有効ですが、pty=True はそれでも結果としてはストリームにマージされるため、実際には大した違いはありません。combine_stderr では問題があるまれなケースを避けるには便利かもしれません(まだ知られたものはありませんが)。