Fabric – Wrappers and more error handling
June 5, 2012
Posted by on I wrote about Fabric’s error handling in this post and there task execution errors were handled like this:
env.warn_only=True def cmd(cmd): if run(cmd).failed: sudo(cmd)
What if the task is a bit more complex and has multilple parts that can go wrong? You might want to abort the execution and do some kind of rollback action.
The task could be aborted by just using env.warn_only=False but then then the task would be aborted before we can do any rollback actions.
Without a wrapper it could be done like this:
env.warn_only=True def multitask(): task1() task2() task3() task4() def task1(): if sudo("apt-get update").failed: rollback() abort("Fail!") if sudo("apt-get -y install php5").failed: rollback() abort("Fail!") def task2(): if sudo("./myscript").failed: rollback() abort("Fail!") def rollback(): sudo("apt-get -y autoremove php5") sudo("rm -r mydir")
Well as you can see this is not very convenient. Instead, you should create a wrapper function for the error handling. This way we don’t need to write those “if run(something).failed” parts.
First we need to do a small import:
from contextlib import contextmanager
Then we can create the wrapper:
env.warn_only=False @contextmanager def rollbackwrap(): try: yield except SystemExit: rollback() abort("Fail!")
Notice that now the warn_only setting is set to False. When the setting is set to False, Fabric will give us the SystemExit exception if there’s errors in the task execution. The wrapper will execute the rollback action when the SystemExit exception is raised. If warn_only is set to True, Fabric will only give a warning and not raise any exceptions.
Now we just need to wrap our task:
@task def multitask(): with rollbackwrap(): task1() task2() task3() task4() def task1(): sudo("apt-get update") sudo("apt-get -y install php5")
Now if anything in one of the tasks fails, the rollback will be executed and then the Fabric execution will be aborted.
Wrappers like this can be of course used for more than just error handling. You can wrap all your settings in them:
@contextmanager def settingwrap(): with settings(warn_only=True, user="simo"): yield @task def deploy(): with settingwrap(): run("whoami") run("hostname")
Complete fabfile for this post:
from fabric.api import * from contextlib import contextmanager env.warn_only=False @contextmanager def rollbackwrap(): try: yield except SystemExit: rollback() abort("Fail!") @task def multitask(): with rollbackwrap(): task1() task2() task3() task4() def task1(): sudo("apt-get update") sudo("apt-get -y install php5") def task2(): sudo("./myscript") def task3(): put("foo.conf","/tmp/") def task4(): sudo("service apache2 restart") def rollback(): sudo("apt-get -y autoremove php5") sudo("rm -r mydir") sudo("rm /tmp/foo.conf") @contextmanager def settingwrap(): with settings(warn_only=True, user="simo"): yield @task def deploy(): with settingwrap(): run("whoami") run("hostname")
Also available here.
Tested with:
Ubuntu 12.04
Fabric 1.4.1
This blog post was inspired by a question on stackoverflow that can be found here:
http://stackoverflow.com/questions/10827112/handling-failures-with-fabric