LoginSignup
34
32

More than 5 years have passed since last update.

fabric でエラー時のロールバック処理をする

Posted at

ところで先日、ぼくの投稿を友人に見てもらう機会があったのですが、
「キモイ、写真がキモイ。ほんとキモイよ?今まで誰も指摘してくれなかったの?」
との絶賛コメントをいただきました。

fabric はシンプルでいて、とても強力なツールです。

しかし、それは使い方によっては危険なツールでもあるということです。
特にぼくのような、キモイ人間のクズが使うときには細心の注意を払う必要があります。

今日もぼくが本番環境に妙なものをデプロイしないか、
奇妙な人たち(上司ほか)が手錠をジャラジャラいわせながら監視しています。

気をつけないと。

ただ、どんなに気をつけていてもエラーというやつは起こるわけで、
だから色々ぶっ壊さないようにエラーハンドリングはちゃんとしてあげないといけません。
設定ファイルを編集した後にどこかでエラーが出たら、
ファイルだけ変えてるせいで2度とデーモンが起動しなくなった、とか、
そういうことが無いようにロールバック処理をしないといけません。

そして、何もなかったかのようにもとに戻して立ち去るのです。
まるでぼくのような人間など、はじめから存在していなかったかのように。

どうでもいい前置きが最悪に長くなりましたが、fabric でのロールバックのやり方です。

基本のやりかた

fabric でエラー時のロールバック処理の基本は、env.warn_only=True でくるむことで
いきなり abort しないようにしといて、エラーが出たら丁寧にロールバック処理をしてあげる、
というやつでしょう。

with settings(warn_only=True):

    backup_important_file()

    run('sed -e "s/foo/bar/g" important_file > tmp_file')
    run('mv tmp_file important_file')

    result = do_something_bad()

    if result.return_code != 0:
        # Rollbacking…
        restore_important_file()

しかし、fabric で実行するタスクは複数ある場合が多いです。

もし、それぞれのタスクのエラー時に必要となるロールバック処理が共通であるとして、
それを素直に実装すると、例えばこんな感じです。

env.warn_only=True

def task1():
    result = run('run command1')
    if result.return_code != 0:
        rollback()

def task2():
    result = run('run command2')
    if result.return_code != 0:
        rollback()

def task3():
    result = run('run command3')
    if result.return_code != 0:
        rollback()

def rollback():
    # Rollbacking...
    restore_everything()

@task
def do_it():
    task1()
    task2()
    task3()

これで十分な場合も多いでしょうが、もう少し冴えた方法も可能です。

ラッパーを用意しておくやりかた

ラッパー関数を用意しておき、コンテキストマネージャを活用することで、
複数のタスクのロールバック処理をまとめることが可能です。

ロールバック処理が複数タスクで共通である場合は、
こちらの方がすっきりします。

env.warn_only=False

def task1():
    run('run command1')

def task2():
    run('run command2')

def task3():
    run('run command3')

def rollback():
    # Rollbacking...
    restore_everything()

@contextmanager
def rollbackwrap():
    try:
        yield
    except SystemExit:
        rollback()
        abort("Error has occurred while running task!")

@task
def do_it():
    with rollbackwrap():
        task1()
        task2()
        task3()
        task4()

エラー処理を個別で行いたい場所では、

with settings(warn_only=True):

を使うと良いでしょう。

参考

以下の記事を大変参考にさせてもらいました。
http://awaseroot.wordpress.com/2012/06/05/fabric-wrappers-and-more-error-handling/

34
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
32