Fabric.
I’d heard about it for at least a year+ but I never got around to it.
I was a little intimidated by it.
The examples seemed so simple, but that was the problem – they seemed too simple, like the examples you run into which look completely uninformative, just a quick blot of code and no explanation.
Well, it turns out fabric really is just that magical.
This is straight life changing!
This is as useful as when I first discovered how much easier it was (for me) to use Git for branching / merging than SVN, or deciding to switch to a linux-like environment for web development.
I’m writing about this today because I was always intimidated by it / thought it would take some time to learn, when there was no reason to be. It’s extremely simple.
Hopefully, it helps somebody out there with the same reservations.
First, let me point you to the official fabric docs where you can find the tutorial and more.
Install fabric.
First, pip install fabric on each machine you need to use fabric on.
pip install fabric
Create the fab file that contains functions
Create a blank file called fabfile.py in the directory you’d like to use the fabric commands from.
For my django project, that’s the directory that contains settings.py and manage.py. My “home base”, or where you’ll find me in an average terminal window.
Create your first fabric command
In just a few lines of code, let’s set up a fabric script that asks the server to pull from the git repository and restart apache.
# import fabrics API functions - self-explanatory once you see
from fabric.api import *
def pushpull():
local('git push') # runs the command on the local environment
run('cd /path/to/project/; git pull') # runs the command on the remote environment
Let’s try running that in bash.
bash$ fab pushpull
It will ask you for a server to connect to that’s running fabric. You will want to enter in your username@host:port here.
Obviously, typing the full URL every time defeats the purpose of an automation script – let’s add it into our fabric file.
Add remote server commands
Let’s add the remote server information to our fabric file. We need to add our server information to our fabric script via the env.hosts parameter.
from fabric.api import *
env.hosts = ['me@example.com:22']
def pushpull():
local('git push') # runs the command on the local environment
run('git pull') # runs the command on the remote environment
env is a dictionary containing various settings, one of which is ‘hosts’ – a list of hosts that all commands will run through.
Now, if we run fab pushpull, we won’t need to type in the server name. If you have SSH keys set up, you won’t need a password.
Add more server setup commands
Next, I want support for working on a development server or production.
Adding this information via a separate function ensures our commands are not tied to specific environments. For example, I use the same functions chained with “dev” or “prod” to determine which environment I am applying the command towards.
Okay, our first roadblock; it turns out that you can not dynamically set env.hosts within a function – it will not register.
def pushpull(host=None):
env.hosts = [host] # this will fail
bash$ fab pushpull:me@example.com # fails
You need to define it in a separate function. Here’s what I’ve done.
I’ve set up a “dev” and “prod” function that sets up the environment prior to running another fab command. For good measure, I also created an “all” command.
dev_sever = 'me@dev.example.com:22'
prod_server = 'me@example.com:22'
def dev():
""" Use development server settings """
env.hosts = [dev_server] # this is the place to add other setup, such as if the django project is in a different directory
# reference this variable later
env['dir'] = '/path/'
def prod():
""" Use production server settings """
env.hosts = [prod_server]
def all():
""" Use all serves """
env.hosts = [dev_server, prod_server]
Now, we can run the command.
bash$ fab dev pushpull bash$ fab prod pushpull bash$ fab all pushpull
Wow, amazing. The possibilities already are limitless!
Some tips – reuse functions.
I’ve found that writing many small functions helps since I’m not always using fabric.
For example, I have a large, full deployment function that…
- commits local changes
- pushes the changes
- pulls the changes on the remote machine
- syncdbs the remote machine
- migrates the remote machine
- restarts apache and other servers
Now, each one of these functions is actually another separate function, since who knows, I might have committed my changes myself and even pushed changes and now I only need a server restart – for me that’s “fab prod deploy”
Passing arguments to fabric functions.
Functions can accept arguments, and are passed to the python functions from the shell via a colon
bash$ fab dev some_func:arg1,arg2,keyword=kwarg
Preventing errors from blowing up
Any non success exit code will stop your function. Use the settings function from the api to allow them to silently pass.
For example, git commit -a will fail if there’s nothing to add.
def foo():
with settings(warn_only=True):
local('git commit -a') # fabric will no longer abort
Keep functions open-ended via *args
There are tons of special case scenarios with django-south and its commands, so I’ve made a migrate fabric command that accepts an arbitrary number of arguments, to support say migrate myapp 0001 —fake
def migrate(*args):
with cd(env['mypath']): # every command will now be run in the path specified
run('python manage.py migrate ' + ' '.join(args))
bash$ fab dev migrate:--all,--fake,0001
Define a way to revert a mistake
Make sure you have a way out of an operation that kills your site.
In my case, I have set up a git revert command as “revert”
def revert():
""" Revert git via reset --hard @{1} """
with cd(env['dir']):
run('git reset --hard @{1}')
arestart() # restarts server
This command would undo the last pull to the working state the server should have been in before we forced a pull.
Use docstrings
The first line of your docstring appears when you list your fab functions via fab –list
One final note:
Check the docs. There are functions such as sudo(‘myfunc’) that will run as root. Very useful.
Great article! Just started using Fabric (had avoided it for similar reasons). I won’t be starting a project without it now. The open-ended arguments is particularly useful – you can easily install remote packages via pip using “fab vps pip_install:django,south…
Hey Timmy,
Thanks for the response. Is this the Timmy I know?
The only worry I have with that open ended arg is… is it possible to inject terminal newlines or something?
Haha, only saw your reply now as I came back to this article to see about using multiple server setups! Yep, it’s the same Timmy. I’m not sure about the open ended arguments; it seems to be quite limited with the semi colon syntax
I am amazed how many time I wasted by ignoring so powerful tool.
Your article helped me a lot to automize deploying of my projects. Thanks!