Monday, August 31, 2009

Covering Deployments with Fabric

I think I've officially decided that Fabric is my favorite Python module ever. Sure there are probably other modules I use constantly and never really care about (sorry datetime!). However, they don't bring me the sheer joy that Fabric brings to me. When I'm developing my personal applications, namely YaBa (this blog) and HateOnYourJob, I tend to perform frequent deployments. Unfortunately deployments can be a time consuming process of bundling my application, pushing it to my server, exploding the archive, putting it in place, restarting Apache and Memcached, and then testing my changes. Not only is that all time consuming, but it's also very prone to human error (i.e. me being a jackass). Now, I thought initally, before I learned of the joys of Fabric that is, that a simple BASH script could easily perform my deployments. So I did at one point have some loosely coupled group of BASH scripts for performing deployments, but it just didn't feel flexible enough. Insert Fabric though, and everything is absolutely effortless.

Currently this is roughly how my deployment path works.

1. Write some code in a git branch
2. Merge it on into the master branch
3. Push
4. Run my fabfile

My fabric file currently consists of:

set(fab_user='f4nt',
fab_hosts=['f4ntasmic.com'],
root='/tmp/',
site='hateonyourjob')

def staging():
set(root='/var/www/domains/hateonyourjob.com/new/')
set(settings='staging_settings.py')

def production():
set(root = '/var/www/domains/hateonyourjob.com/www/')
set(settings='production_settings.py')

def deploy():
local('git archive --format=tar HEAD | gzip > $(site).tar.gz')
sudo('chown -R f4nt.f4nt $(root)')
sudo('rm -rf $(root)$(site).OLDER')
sudo('mv $(root)$(site).OLD $(root)$(site).OLDER')
sudo('mv $(root)$(site) $(root)$(site).OLD')
run('mkdir $(root)$(site) -p')
put('$(site).tar.gz', '$(root)$(site)/$(site).tar.gz')
run('cd $(root)$(site) && tar zxf $(site).tar.gz')
sudo('rm -f $(root)$(site)/localsettings.py')
put('$(settings)', '$(root)$(site)/localsettings.py')
run('mkdir -p $(root)$(site)/cache')
sudo('chown -R apache:apache $(root)')
local('rm -f $(site).tar.gz')
restart()

def restart():
sudo('/etc/init.d/httpd reload')
sudo('/etc/init.d/memcached restart')

This file is executed by running:

fab production deploy

I can also replace 'production' with 'staging' to deploy to my staging instance of HOYJ. Also, along with it deploying to my staging instance, it'll also put my staging settings file in place instead of my production settings file to ensure that I don't have database conflicts. Some people like to timestamp each of their deployments, and have a rotation system in place built around that. Personally, I feel 3 deployments (current, old, and older) is typically plenty. If I need to fall back further than that I have a git repository that I can go mucking around in. Keeping 50 some odd deployments is just a waste of disk space in my opinion, but your feelings might vary.

I think what I enjoy about Fabric is how easy it is to follow and use. I barely know anything about Fabric, and have barely read any documentation, but I can do deployments with one quick command. Within 5 minutes my deployment is done, and it's done right, everytime. That leaves me more time to focus on developing rather than futzing around with deployments where I'm likely going to screw something up since it's boring and I'll rush through it. :) Anyways, hope this points a couple more people towards Fabric that hadn't previously heard of it. Maybe you Capistrano users might even find it useful :)

Wednesday, August 05, 2009

Django Admin Actions

As most people who would care know, Django 1.1 was recently released. There wasn't a whole lot in this release that I was exactly clamoring for, but I always welcome a rash of bug fixes and the occasional new feature. It's certainly nothing to complain about when my favorite web framework reaches a new version, and obtains a bit more stability along the way. When I first read about customizable Administrative Actions I didn't really care. I guess I didn't really see how it'd affect me that much, if at all. Then the other day it hit me in the face like a ton of bricks.

I'm currently working on an online catalog application. It's not quite a store, just a catalog where people can browse items, and then call in an order. Think of it just like your old school print catalogs, just this one's a bit more convenient. I'm not going to get into why it's not going to just be a store, it's just not currently. So anyways, I'm asked to build this catalog, and I'm trucking right along with it. The customer in question however needs to be able to "feature" items on a semi-regular basis. Now I don't really know how many they're going to feature, how often, or anything of the sort. Still, I thought, if I had to 'feature' 10 different items, it'd be a real pain to do it manually. Thankfully Django 1.1 brought me a solution just in time with the customizable administrative actions. I was able to write the following function:

def feature_item(self, request, queryset):
count = 0
for x in queryset:
x.is_featured = True
count += 1

if count == 1:
message = "1 product was"
else:
message = "%s products were" % count
self.message_user(request, "%s successfully featured" % message)
feature_item.short_description = "Feature selected items"

That was placed in my ProductAdmin class in admin.py, and then all I needed was:

actions = ['feature_item']

a bit further down in my ProductAdmin. Now users can view their products, select a few of them, grab "Feature items" from a drop down box, and click "Go". Presto, bunch of new items featured on their front page. They can also "unfeature" a bunch of items in the same fashion. Saves a bunch of time for me with how easy it was to write that, and a bunch of time for the user later on down the road. I definitely recommend reading the documentation to get a better understanding of the admin actions as well. It's worth the read.