94

When I define a task to run on several remote servers, if the task runs on server one and exits with an error, Fabric will stop and abort the task. But I want to make fabric ignore the error and run the task on the next server. How can I make it do this?

For example:

$ fab site1_service_gw
[site1rpt1] Executing task 'site1_service_gw'

[site1fep1] run: echo 'Nm123!@#' | sudo -S route
[site1fep1] err:
[site1fep1] err: We trust you have received the usual lecture from the local System
[site1fep1] err: Administrator. It usually boils down to these three things:
[site1fep1] err:
[site1fep1] err:     #1) Respect the privacy of others.
[site1fep1] err:     #2) Think before you type.
[site1fep1] err:     #3) With great power comes great responsibility.
[site1fep1] err: root's password:
[site1fep1] err: sudo: route: command not found

Fatal error: run() encountered an error (return code 1) while executing 'echo 'Nm123!@#' | sudo -S route '

Aborting.
tshepang
  • 12,111
  • 21
  • 91
  • 136
Mingo
  • 1,613
  • 2
  • 16
  • 20

7 Answers7

148

From the docs:

... Fabric defaults to a “fail-fast” behavior pattern: if anything goes wrong, such as a remote program returning a nonzero return value or your fabfile’s Python code encountering an exception, execution will halt immediately.

This is typically the desired behavior, but there are many exceptions to the rule, so Fabric provides env.warn_only, a Boolean setting. It defaults to False, meaning an error condition will result in the program aborting immediately. However, if env.warn_only is set to True at the time of failure – with, say, the settings context manager – Fabric will emit a warning message but continue executing.

Looks like you can exercise fine-grained control over where errors are ignored by using the settings context manager, something like so:

from fabric.api import settings

sudo('mkdir tmp') # can't fail
with settings(warn_only=True):
    sudo('touch tmp/test') # can fail
sudo('rm tmp') # can't fail
Community
  • 1
  • 1
Will McCutchen
  • 13,047
  • 3
  • 44
  • 43
31

As of Fabric 1.5, there is a ContextManager that makes this easier:

from fabric.api import sudo, warn_only

with warn_only():
    sudo('mkdir foo')

Update: I re-confirmed that this works in ipython using the following code.

from fabric.api import local, warn_only

#aborted with SystemExit after 'bad command'
local('bad command'); local('bad command 2')

#executes both commands, printing errors for each
with warn_only():
    local('bad command'); local('bad command 2')
Chris Marinos
  • 965
  • 8
  • 8
  • Which version of fabric are you using? I just retested with Fabric==1.6.2, and it works fine. – Chris Marinos Jul 08 '14 at 18:26
  • Possibly, I am using Fabric==1.9.0 and it does not work for me – cevaris Jul 09 '14 at 15:16
  • Just tested on 1.9.0 too. What is your output when you try the example code from my updated comment? – Chris Marinos Jul 15 '14 at 13:33
  • If you don't want to print the warnings/errors, you can also use the [hide](http://docs.fabfile.org/en/1.14/api/core/context_managers.html#fabric.context_managers.hide) context manager: `with hide('everything'):` – Niko Föhr Dec 25 '17 at 11:46
13

You can also set the entire script's warn_only setting to be true with

def local():
    env.warn_only = True
Rawkcy
  • 139
  • 1
  • 3
11

You should set the abort_exception environment variable and catch the exception.

For example:

from fabric.api        import env
from fabric.operations import sudo

class FabricException(Exception):
    pass

env.abort_exception = FabricException
# ... set up the rest of the environment...

try:
    sudo('reboot')
except FabricException:
    pass  # This is expected, we can continue.

You can also set it in a with block. See the documentation here.

ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
  • Thanks for this, but one question - is it possible access/pass in the current fabric env dict as-defined when the exception occurred? (So I can print some specific settings with the exception.) – notbrain Apr 07 '15 at 00:42
  • @Brian: Couldn't you just check the `fabric.api.env` within your `except` block? – ArtOfWarfare Apr 07 '15 at 10:57
  • @ArtOfWarefare Ahh silly me I was trying to avoid wrapping all my tasks in a try/except and instead just set up the `env.abort_exception=MyException` so I could run my own fail. It sort of "works" if I use a function instead of a class (satisfies the callable req for `abort_exception`) but I'm still working on some other issues with that approach. – notbrain Apr 07 '15 at 17:20
  • @Brian: So inside the body of that function check what `fabric.api.env` is. – ArtOfWarfare Apr 08 '15 at 02:06
11

In Fabric 2.x you can just use invoke's run with the warn=True argument. Anyway, invoke is a dependency of Fabric 2.x:

from invoke import run
run('bad command', warn=True)

From within a task:

from invoke import task

@task
def my_task(c):
    c.run('bad command', warn=True)
Qlimax
  • 5,241
  • 4
  • 28
  • 31
7

In Fabric 1.3.2 at least, you can recover the exception by catching the SystemExit exception. That's helpful if you have more than one command to run in a batch (like a deploy) and want to cleanup if one of them fails.

zimbatm
  • 732
  • 1
  • 7
  • 13
  • +1: Tested - this also works in Fabric 1.9.0. After catching this, you can check the `SystemExit`'s message or code for more details. – ArtOfWarfare Aug 13 '14 at 18:05
  • Even better than catching `SystemExit`, set `abort_exception` to a different exception, so that you don't accidentally catch exceptions which have nothing to do with Fabric. See my answer for an example: http://stackoverflow.com/a/27990242/901641 – ArtOfWarfare Jan 16 '15 at 18:16
-5

In my case, on Fabric >= 1.4 this answer was the correct one.

You can skip bad hosts by adding this:

env.skip_bad_hosts = True

Or passing the --skip-bad-hosts flag/

Community
  • 1
  • 1
Christian Vielma
  • 15,263
  • 12
  • 53
  • 60