Easy Fabric Deployment, Part 1: Git/Mercurial and SSH
We’re firm believers in the practices described by the Continuous Integration method of software engineering. Among those are:
- Maintain a code repository
- Automate the build
- Automate deployment
We use git for our code repositories and Fabric to automate our build/deployment process. The tiny bit of overhead it take to write out a Fabric script pays off very quickly against the tedium and error-prone practice of manually building/deploying. In building our “fabfile”, we encountered a couple of issues that took a little head-scratching to work out.
Git and SSH Keys
Git, like Mercurial and others, confirms your credentials via an private/public key pair when used over SSH. We use gitosis to manage our private repositories, so managing everyone’s keys isn’t much of an issue. The problem comes when developers need to start pulling the repository on different machines. Our developers all push to a central “origin” and our development server runs a clone of that repository.
We want every push to the master branch to automate the build and deployment. The first thought is to manage this with post-commit hooks, but that raises some security concerns. I want the git user that owns all the repositories to have as few permissions as possible. It’s job is to act as a gatekeeper for our repositories, not handle deployment across any number of servers.
The simple workaround is a Fabric script that allows our developers to use one command to push, build and deploy. Our script does the following:
- Push local git repository to origin
- Pull development server git repository from origin
- Install any 3rd party libraries as specified in the pip requirements file
- Run any necessary migrations
- Run
syncdb - Restart the WSGI daemon
For the most part, those are trivial commands. The one that gets us into trouble is pulling to our development server(s). Our developer’s local SSH keys aren’t available server-side. Having to manage a whole new set of SSH keys or require them to copy their private keys to the server was not desirable.
The good news is that, ssh provides a way to forward your authentication agent connection. From the ssh man page:
-A Enables forwarding of the authentication agent connection. This
can also be specified on a per-host basis in a configuration
file.
Agent forwarding should be enabled with caution. Users with the
ability to bypass file permissions on the remote host (for the
agent’s Unix-domain socket) can access the local agent through
the forwarded connection. An attacker cannot obtain key material
from the agent, however they can perform operations on the keys
that enable them to authenticate using the identities loaded into
the agent.
The bad news is that neither Fabric, nor paramiko (the guts of Fabric’s SSH implementation) expose this switch. The fix is simply to use your local shell to run any commands over SSH. We created a Fabric function that looks like this:
env.hosts = ['our.development.server'] # ... def sshagent_run(cmd): """ Helper function. Runs a command with SSH agent forwarding enabled. Note:: Fabric (and paramiko) can't forward your SSH agent. This helper uses your system's ssh to do so. """ for h in env.hosts: try: # catch the port number to pass to ssh host, port = h.split(':') local('ssh -p %s -A %s "%s"' % (port, host, cmd)) except ValueError: local('ssh -A %s "%s"' % (h, cmd))
Now anywhere you would have called run() in Fabric, you can call sshagent_run() and it works as expected.
The Authentication Agent
There is one more step you may need to take on your local computer for this technique to work. You’ll need to ensure that your identity has been added to the authentication agent. As far as I can tell, current versions of Ubuntu do this automatically. For other operating systems (OS X and other Linux variants), you may need to do this manually. You can check by running ssh-add -l from the command line. If you get a line back containing the path to your private key, you can skip this step. If you don’t, you’ll want to run ssh-add to add your key to the list. Since this isn’t a persistent setting, you’ll probably want to add it to your ~/.bashrc or someplace else it can run on login.
With your Fabric script and authentication agent setup, you can run automated build and deployments across any number of servers without ever worrying about which machine you are on and where your SSH key lives.
Have you solved this issue in a different way? Leave us a comment, we’d love to hear your thoughts.
Comments
Got something to say?
Our Products
Categories
- accessiblity
- code
- company news
- django
- gondola
- open source
- portfolio
- presentation
- pro tip
- review
- screencast
- seo
- software
- subversion
- trailmapping
- wordpress
Archives
- July, 2010
- June, 2010
- May, 2010
- April, 2010
- February, 2010
- December, 2009
- November, 2009
- October, 2009
Elsewhere
What we’ve been up to online
-
Just launched a Flask/App Engine mini-site we've been tinkering on http://emailed-me.appspot.com/
Pete, 14 hours, 46 minutes ago -
created repository Emailed-Me-
Pete, 14 hours, 54 minutes ago -
Our first iPhone development project hit the App Store last week and is already over 1k users! Check them out @takemyspot #iphone #geodjango
Pete, 3 weeks ago -
Love the new sites! RT @welikesmall: We just launched two new sites. http://post.ly/mGoq
Pete, 3 weeks, 1 day ago -
Pro tip: Using pip safely for automated deployment (no more pesky prompts) http://bit.ly/b5zsPa
Pete, 4 weeks, 1 day ago -
commented on justquick/django-mailfriend
Pete, 1 month ago -
RT @unbracketed: Excited to have @mitsuhiko joining us for some work this summer :)
Pete, 1 month ago -
New blog post: managing supervisord with upstart http://bit.ly/db3p5N
Pete, 1 month ago -
Troubleshooting OpenID is just like user/password. Except you have 5 of them and and you don't know which one is failing, and 3 login pages
Pete, 1 month, 1 week ago -
This gets very interesting around 42 min. Using javascript to snoop inside firewalled networks http://bit.ly/aNVPc5
Pete, 1 month, 2 weeks ago -
The final tally is in. 8 Lincoln Loopers attending DjangoCon. 3 US, 4 EU, and 1 NZ. Looking forward to it!
Pete, 1 month, 2 weeks ago -
Twitter / Dustin Curtis: I'm flying to Madrid tomor ...
Dustin Curtis travels to Berlin, Bangkok & Madrid in exchange for design services as the result of a late night tweet.
Pete, 1 month, 2 weeks ago -
created branch ubuntu-8.04 at lincolnloop/fab-pave
Pete, 1 month, 3 weeks ago -
created repository fab-pave
Pete, 1 month, 3 weeks ago -
pushed to master at lincolnloop/django-mailfriend
Pete, 1 month, 3 weeks ago


Thanks for the tip,
deployment using fabric and pip/venv looks like hot topic at the moment on djangonauts blogs. In our company we deploy our apps using pip and fabric but we usually write custom fabric script for each project since deployment procedure differs each time.
A point that might block the deployment procedure you refer is the requirement of pip for installed vcs libraries/packages/binaries when external packages (-e) are inside pip requirement files. Pip provide an alternative way (package bundles) to this which we currently use to deploy applications in strict environments (e.g. shared hosting). Bundles cover our needs in most cases but they are not so flexible (frequently package upgrades/installations require much time to create the bundle and deploy it).
Hello from Russia!
Can I quote a post in your blog with the link to you?
Polprav, you may not “re-post” or copy the full post verbatim, but feel free to quote a few sentences with a link back to the original source article.
Thanks for reading!
Thanks! saved my day!
I also had to add env.user to it because my deployment user is different that my local user.
for h in env.hosts:
try:
# catch the port number to pass to ssh
host, port = h.split(’:’)
local(‘ssh -p %s -A %s@%s ”%s”’ % (port, env.user, host, cmd))
except ValueError:
local(‘ssh -A %s@%s ”%s”’ % (env.user, h, cmd))