Don’t feel like setting up Jenkins you lazy bum? Fine. Try this on for size: use a Github service hook to ping a Django view which runs a bash script out of process. Sound like a bad idea? Probably, but bad is a relative thing you see… here’s how:
- Gonna need the at command. Do that now: apt-get install at
- Gonna need two bash scripts: delay_deploy.sh and deploy.sh
- Gonna need a view to receive the hook
First, let’s do the deploy.sh script:
#!/bin/bash cd /path/to/repo git pull origin master service apache2 restart # or anything else you might need |
Second, let’s do the delay_deploy.sh script:
#!/bin/bash at -f '/path/to/deploy.sh' now |
If you have a standard Django/Apache/mod_wsgi setup, we will have a problem running this script from a view because Apache is running under a non-root user (as it should be). Usually it is under the user www-data, but you can double check with ps aux | grep apache. The same holds for Nginx, lighttpd, etc…
Third, let’s permit www-data (or whoever) to sudo delay_deploy.sh (which will running a static, out of process command via at). This requires editing the /etc/sudoers file, so nano /etc/sudoers and add this line:
www-data ALL= NOPASSWD: /path/to/delay_deploy.sh |
Now, www-data can run delay_deploy.sh as root, but only delay_deploy.sh and nothing else. This is much safer that allowing www-data to run /usr/bin/at or something more generic. Also, don’t forget to run chmod +x on both scripts.
The reason you can’t just hit the deploy.sh script is pretty simple: the server will wait on the command to finish, but the command will kill the server. Not good. So our two script method fixes this: the delay script triggers an out of process deploy script. Also, its good to keep sudo power very, very specific.
Fourth, let’s work on that view. I’ll leave it up to the reader to decide where to put it and how to secure it (hint: maybe check for a regularly changed secret GET param key and restrict to Github’s IP address):
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def do_deploy(request): """ Smile. Life's just gotten easier. """ import simplejson import subprocess from django.http import HttpResponse, Http404 if request.method != 'POST': raise Http404 if not 'payload' in request.POST.keys(): raise Http404 # might raise 404 if secret GET key isn't good # or requesting IP isn't whitelisted payload = simplejson.loads(request.POST['payload']) out_json = {'status': 'failed'} # only trigger if master branch receives a commit if payload['ref'] == 'refs/heads/master': # DO NOT use any user input when calling scripts, as # this is naughty enough already subprocess.call('sudo /path/to/delay_deploy.sh', shell=True) out_json['status'] = 'success' return HttpResponse(simplejson.dumps(out_json), content_type='application/json') |
Fifth, and finally, set up your Post Receive Hook or custom Service Hook with Github to hit the URL for the above view. If you need to debug, check out hurl.it for some handy dandy POST testing action.
Another warning: letting HTTP requests trigger sudo’d events is very, very dangerous. While as far as I know, this particular method is mostly safe, it most certainly isn’t best practices. Light me up in the comments.