<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Dave Dash</title>
 <link href="http://davedash.com/tag/deployment/atom.xml" rel="self"/>
 <link href="http://davedash.com/tag/deployment"/>
 <updated>2012-01-17T21:54:19-08:00</updated>
 <id>http://davedash.com/</id>
 <author>
   <name>Dave Dash</name>
   <email>dd+atom1@davedash.com</email>
 </author>

 
 <entry>
   <title>A stitch in Fabric saves time</title>
   <link href="http://davedash.com/2009/03/02/a-stitch-in-fabric-saves-time/"/>
   <updated>2009-03-02T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/03/02/a-stitch-in-fabric-saves-time</id>
   <content type="html">&lt;p&gt;Each day as I grow as a developer I pick up better habits.  One is automating anything that I'll have to do more than once.&lt;/p&gt;

&lt;p&gt;Therefore, deploying important projects has evolved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit files live on the site.&lt;/li&gt;
&lt;li&gt;FTP files from my local machine onto the live site and do tweaking as needed.&lt;/li&gt;
&lt;li&gt;Develop using a cross-platform capable framework and deploy live via rsync.&lt;/li&gt;
&lt;li&gt;One step deployment.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;I looked at a few methods for performing one-step deployment.  Shell scripts seemed to basic, and things like Puppet seemed to large a scale.  So I looked into Capistrano and &lt;a href=&quot;http://www.nongnu.org/fab/&quot;&gt;Fabric&lt;/a&gt;, and settled on &lt;a href=&quot;http://www.nongnu.org/fab/&quot;&gt;Fabric&lt;/a&gt;, because it was barebones and python.&lt;/p&gt;

&lt;p&gt;I'm at a stage with my project where I need to test it on an external server, so rather than uploading it and winging it I actually thought about a few things.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;The task&lt;/h3&gt;

&lt;p&gt;I was thinking the ideal situation is a single command line that I can type that will upload my files to the server.  Furthermore it should upload first to a new directory and then symlink and restart the server to do the cutover.  I can easily write another script to rollback.&lt;/p&gt;

&lt;p&gt;The way I setup my Nginx and Apache servers was they would look for the symlinks: staging, test, production to serve up the respective environments (I'm using a single host for this example).&lt;/p&gt;

&lt;h3&gt;Directory Structure&lt;/h3&gt;

&lt;p&gt;I use a very particular directory structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$WWW_APPS/mysite.com/&lt;/code&gt; - is my root app deployment directory.

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;releases/&lt;/code&gt; - each code push is stored in a unique directory

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100/&lt;/code&gt; - the directory name is the &lt;code&gt;svn&lt;/code&gt; revision, but is a series of precise &lt;code&gt;svn&lt;/code&gt; exports, not a checkout.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging&lt;/code&gt; - this symbolic link links to a specific revision directory that has been designated for staging purposes.  This might be the last directory in &lt;code&gt;releases/&lt;/code&gt;, e.g. &lt;code&gt;$WWW_APPS/mysite.com/releases/100/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/&lt;/code&gt; - any config files go here (e.g. nginx or apache)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysite.com/&lt;/code&gt; - the actual Django project&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/&lt;/code&gt; - any utility scripts, mostly database model changes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site-packages/&lt;/code&gt; - any related libraries that I need, this isn't the best approach I need to investigate &lt;code&gt;virtualenv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static/&lt;/code&gt; - all my static assets go here&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging.rollback&lt;/code&gt; - the former &lt;code&gt;staging&lt;/code&gt; symlink is demoted to &lt;code&gt;staging.rollback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$WWW/mysite.com/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;staging&lt;/code&gt; - This is where the static assets get served from according to nginx.  It's a symbolic link to &lt;code&gt;$WWW_APPS/staging/static&lt;/code&gt;.  This link does not change during deployment.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uploads_staging/&lt;/code&gt; - This directory contains uploads (user data).  It requires manual adjustment when there are data changes (deletes, adds, updates).  For the most part it can stay unchanged from one deployment to the next.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Subversion&lt;/h3&gt;

&lt;p&gt;I'm currently using subversion to maintain my code.  If you are starting a project anew, I suggest using &lt;code&gt;git&lt;/code&gt; as it is designed with branching in mind.  The script below should be adaptable for &lt;code&gt;git&lt;/code&gt; instead of &lt;code&gt;svn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That being said, when I deploy code, I use the revision number as the directory name for that deployment (e.g. &lt;code&gt;$WWW_APPS/mysite.com/releases/100/&lt;/code&gt; for &lt;code&gt;r100&lt;/code&gt;) .&lt;/p&gt;

&lt;p&gt;This forces me to commit my changes and not deploy code that is not checked in.&lt;/p&gt;

&lt;p&gt;Other viable alternatives would be time-stamped directories.  Anything that is unique between deployments will be sufficient.&lt;/p&gt;

&lt;h3&gt;The &lt;code&gt;fabfile.py&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Here's the &lt;code&gt;fabfile.py&lt;/code&gt; I constructed.  It's by no means final&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
def staging():
    &quot;Pushes current code to staging, hups Apache&quot;
    # get the build number    
    local('svn up mysite.com')
    
    config.svn_version   = svn_get_version()
    
    if not config.svn_version:
        abort()
    
    config.static_path   = '/var/www/static.mysite.com'
    config.svn_path      = 'http://svn.mysite.com/trunk'
    config.svn_export    = 'svn export -q -r %(svn_version)s'
    
    run('mkdir %(path)s', fail='abort')
    
    # svn export mysite.com to path 
    run('%(svn_export)s %(svn_path)s/mysite.com %(path)s/mysite.com', fail='abort')
    
    # svn export site-packages to site-packages
    run('%(svn_export)s %(svn_path)s/site-packages %(path)s/site-packages', fail='abort')
    
    # svn export mysite.com to path 
    run('%(svn_export)s %(svn_path)s/scripts %(path)s/scripts', fail='warn')
    
    # svn export configs
    run('%(svn_export)s %(svn_path)s/config %(path)s/config', fail='abort')
    
    # export /var/www/static.mysite.com/releases/%(svn_version) 
    run('%(svn_export)s %(svn_path)s/static %(path)s/static', fail='abort')
    
    # symlink to images from /var/www/static.mysite.com/staging/images/menuitems/* new release dir
    run(&quot;rm -r %(path)s/static/images/menuitems&quot;, fail=abort)
    run(&quot;ln -s %(static_path)s/menuitems_staging %(path)s/static/images/menuitems&quot;, fail=abort)

    # rotate &quot;staging&quot; symlinks
    run('rm %(releases_path)s/staging.rollback', fail='warn')
    run('mv %(releases_path)s/staging  %(releases_path)s/staging.rollback', fail='warn')

    # staging sym to new destination
    run('ln -s %(path)s %(releases_path)s/staging', fail='abort')
    
    # server is hup'd
    invoke(hup)

def rm_cur_rev():
    config.svn_version   = svn_get_version()
    run('rm -rf %(path)s', fail='abort')

def hup():
    sudo('/etc/init.d/apache2 restart')
    sudo('/etc/init.d/nginx restart')
    
    
def svn_get_version():
    from subprocess import Popen, PIPE
    output = Popen([&quot;svn&quot;, &quot;info&quot;, &quot;mysite.com&quot;], stdout=PIPE).communicate()[0]
    return output.partition('Revision: ')[2].partition('\n')[0]



config.fab_hosts = ['mysite.com']
config.fab_user = 'builder'
config.releases_path = '/var/www_apps/mysite.com'
config.path          = '%(releases_path)s/releases/$(svn_version)'


&lt;/textarea&gt;&lt;/div&gt;


&lt;h3&gt;Final Thoughts&lt;/h3&gt;

&lt;p&gt;There's room for improvement, but this is a start.  &lt;code&gt;virtualenv&lt;/code&gt; looks like another avenue I might want to explore (especially when it comes time to upgrade Django, python, MysqlDB or PIL).  This also doesn't take care of priming the server with necessary packages - again, something &lt;code&gt;virtualenv&lt;/code&gt; could make easier for me.&lt;/p&gt;

&lt;p&gt;However, this file was not too difficult to create.  I sat down, thought about the ideal solution, implemented the steps and iterated on that until the script worked.  I have pushed 19 revisions of code so far, mostly to test this process, but also to make necessary adjustments.  I've probably saved countless hours of logging in and doing things manually.&lt;/p&gt;

&lt;p&gt;How do you deploy your apps?&lt;/p&gt;
</content>
 </entry>
 

</feed>

