鱒身(Masu_mi)のブログ

知った事をメモする場所。

Fabricを使う上で知ってると嬉しいN 個の事

Fabric を使っていて早めに知りたかった事をまとめた。

タスクに説明をつける

Fabricはタスクのドキュメンテーション文字列(docstring) をタスクの説明として利用する。 そのためfabfile.py を以下の様に定義するとfab –list の際に説明が表示される。

# coding: utf-8
from fabric.api import task, run

@task
def setup():
  """

  空行を除く最初の1行が表示される
  ここは表示されない
  """
  run("hostname")

このファイルでは以下の様になる。

$ fab --list
Available commands:

    setup  空行を除く最初の1行が表示される

タスクはモジュールを元に階層構造を取る

タスク#名前空間 に書かれているが。 まずタスクを定義したモジュールkusa を作成する。

$ mkdir kusa
$ vim ./kusa/__init__.py
# coding: utf-8
# __init__.py
from fabric.api import task, run

@task
def www():
  """ WWW """
  run("hostname")

以下の様に fabfile.py でタスクを定義したモジュールをロードするタスクが階層化される。

#...
import kusa

@task
def setup():
  """ 設定 """
  run("hostname")

実行結果は以下の様になる。

$ fab --list
Available commands:

    setup     設定
    kusa.www  WWW

タスクを複数指定したときの実行順序

基本的に、タスク・関数は以下の様に実行される。

  1. fab コマンドに与えたタスクを左から実行する

  2. 各タスクはホスト分繰り返し実行される
    • @parallel デコレータが使われている場合は並列実行される
  3. 各タスクが内部で実行する関数・タスクは関数として実行される
    • 呼ばれた関数に@serial デコレータが使われている場合 @parallelによって並列実行されている場合も同時に実行されない様になる
    • 呼ばれた関数に` @runs_once デコレータが使われている場合 実行中に始めて呼ばれた場合を除いて実行されない
    • 呼ばれた関数に@parallel デコレータが使われている場合 特に意味がない

試しに以下の様にfabfile.py を書いて実行する。

@task
def call_test():
    func_print()
    once_func_print()
    task_print()
    once_task_print()
    parallel_func_print()
    parallel_task_print()
@task
def task_print():
    print "IN TASK_LS"
@runs_once
@task
def once_task_print():
    print "IN ONCE_TASK_LS"
def func_print():
    print "IN FUNC_LS"
@runs_once
def once_func_print():
    print "IN ONCE_FUNC_LS"
@parallel
def parallel_func_print():
    print "IN PARALLEL_FUNC_LS: %s"%env.host
@task
@parallel
def parallel_task_print():
    print "IN PARALLEL_TASK_LS: %s"%env.host
    serial_task_print()
    func_print()
@task
@serial
def serial_task_print():
    print "IN SERIAL TASK_PRINT: %s"%env.host
$ fab call_test task_print call_test once_task_print parallel_task_print
[localhost] Executing task 'call_test'
IN FUNC_LS
IN ONCE_FUNC_LS
IN TASK_LS
IN ONCE_TASK_LS
IN PARALLEL_FUNC_LS: localhost
IN PARALLEL_TASK_LS: localhost
IN SERIAL TASK_PRINT: localhost
IN FUNC_LS
[yazawa.niko] Executing task 'call_test'
IN FUNC_LS
IN TASK_LS
IN PARALLEL_FUNC_LS: yazawa.niko
IN PARALLEL_TASK_LS: yazawa.niko
IN SERIAL TASK_PRINT: yazawa.niko
IN FUNC_LS
[localhost] Executing task 'task_print'
IN TASK_LS
[yazawa.niko] Executing task 'task_print'
IN TASK_LS
[localhost] Executing task 'call_test'
IN FUNC_LS
IN TASK_LS
IN PARALLEL_FUNC_LS: localhost
IN PARALLEL_TASK_LS: localhost
IN SERIAL TASK_PRINT: localhost
IN FUNC_LS
[yazawa.niko] Executing task 'call_test'
IN FUNC_LS
IN TASK_LS
IN PARALLEL_FUNC_LS: yazawa.niko
IN PARALLEL_TASK_LS: yazawa.niko
IN SERIAL TASK_PRINT: yazawa.niko
IN FUNC_LS
[localhost] Executing task 'parallel_task_print'
[yazawa.niko] Executing task 'parallel_task_print'
IN PARALLEL_TASK_LS: yazawa.niko
IN SERIAL TASK_PRINT: yazawa.niko
IN FUNC_LS
IN PARALLEL_TASK_LS: localhost
IN SERIAL TASK_PRINT: localhost
IN FUNC_LS

Done.

eagerly_disconnect でセッションを切る

以下を設定しておかないとfab コマンド全体が完了するまで各ホストへのセッションが切断されない。 そのためホストが多いとエフェメラルポートが足りなくなりタスクが完了しなかったりする。

from fabric.api import env
env.eagerly_disconnect = True

get/put のエラーハンドリング

get, put 関数を使いSFTPを利用しファイル・ディレクトリを転送する。 これは失敗する事もありえるので、ちゃんと確認したい。 以下みたいに検知する。

@task
def get_archive():
  r = get("archive", "$(path)s.$(host)s")
  if not r.succeded:
    for remote_path in r.failed:
      print "[ERROR]%s"%remote_path

必要ない事は出力しない

Fabricって何でもかんでも出力してとても見にくい。 peco 使ってフィルタリングしながら探したりするか逆に出力を抑えて欲しい物だけ明示的に出力するのも手段としては取り得る。 出力を抑える方法 は以下の様にする。

@task
def remote_ls():
  with hide("running", "stdout"):
    run("ls -la")

@task
def silent_pwd():
  import fabric.state.output
  output["running"] = False
  run("pwd")

password の処理

以下の様にpasswordを設定しておいて、入力を省く。

import getpass
env.password = getpass.getpass()
@task
def sudo_ls():
  sudo("ls -la")

インタラクティブな操作を行なう

以下の様にする事で特定ファイルを手作業で変更する等の作業を自動化されたタスクに埋込む事が可能になる。 ただしエラーハンドリングされないので扱い辛い。 引数を与えるとユーザが操作可能になる前に実行される。だから以下の様にすると編集完了後に強制ログアウトさせられる。

from fabric.operations import open_shell
@task
def edit():
    open_shell("vim ~/.bashrc && exit")

sshで鍵ファイルを利用する

sshの鍵ファイルはenv で設定する。ForwardAgent しているときは邪魔になるので注意が必要。

env.key_filename = "~/.ssh/id_rsa"
@task
def hostname():
  run("hostname")