capistrano (3.10.1)のSSHKitを利用する部分をコードリーディングしてみました。

#onや#run_locallyはCapistrano::DSLに定義されており、lib/capistrano/dsl.rb内で当モジュールをextendしています。

#onはSSHKit::Coordinator#eachを呼び出し、#run_locallyはSSHKit::Backend::Local#runを呼び出します。まずは前者の#onから追っていきます。

SSHKit::Coordinator#eachではランナーの#executeを呼び出します。

以下、デフォルトのSSHKit::Runner::Parallel#executeのケースを追っていきます。

SSHKit::Runner::Parallel#executeはThreadをホスト数分呼び出して、スレッド内でbackendのrunメソッドを実行しています。

backendメソッドはSSHKit::Runner::Abstract#backendに定義されており、localでなければSSHKit.config.backend=SSHKit::Backend::Netsshのインスタンスを返します。

SSHKit::Backend::Netssh#runメソッドはまず、SSHKit::Backend::Abstract#runを呼び出します。#runはinstance_execでブロックを評価します。

SSHKit::Backend::Netsshのインスタンスのコンテキストで実行されるため、 ブロック内のexecuteはSSHKit::Backend::Netsshのメソッド呼び出しになります。#executeは#create_command_and_executeを呼び出し、さらに#execute_commandを呼び出します。

SSHKit::Backend::Netssh#execute_commandはcmd.to_commandで取得したコマンド文字列をSSHで実行します。

cmd変数はSSHKit::Backend::Abstract#command経由で生成されたSSHKit::Commandのインスタンスです。

SSHKit::Command#to_commandのネスト部分は以下のような処理になっています

  • cdによるカレントディレクトリの操作
  • umaskによるパーミッションの操作
  • withによる環境変数の操作
  • sudoによる実行ユーザの操作
  • nohupによるバックグラウンド処理
  • sgによる実行グループの操作
  • 指定したコマンド引数のコマンド文字列化

最後のコマンド文字列化では、SSHKit::CommandMapのインスタンスであるSSHKit.config.command_map経由で第一引数を変換し、第二引数以降の文字列をArray#joinで連結しています。第一引数の変換によって具体的なパス指定やbundlerなどのprefixを自動付与することを実現しています。

おまけ

ちなみにwithinやwithなどのメソッドもSSHKit::Backend::Abstractで定義されています。#withinの場合は@pwdに設定し、#executeのinオプションに入れることでカレントディレクトリの操作をしています。#withも同様に@envによって環境変数の操作を行います。

#makeや#rakeも定義されており、単純にexecuteのショートカットになっています。

#captureは#executeとほぼ同じですが、SSHKit::Command#full_stdoutによって標準出力を取得して返しています。