4221f80fe7ee — Peter Sanchez 13 years ago
Initial files...
3 files changed, 470 insertions(+), 0 deletions(-)

A => BSD-LICENSE
A => README.txt
A => fabfile.py
A => BSD-LICENSE +32 -0
@@ 0,0 1,32 @@ 
+Copyright (c) 2010, Peter Sanchez <petersanchez@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or 
+without modification, are permitted provided that the 
+following conditions are met:
+
+ * Redistributions of source code must retain the above 
+   copyright notice, this list of conditions and the 
+   following disclaimer.
+
+ * Redistributions in binary form must reproduce the above 
+   copyright notice, this list of conditions and the following 
+   disclaimer in the documentation and/or other materials 
+   provided with the distribution.
+
+ * Neither the name of Peter Sanchez nor the names of its 
+   contributors may be used to endorse or promote products 
+   derived from this software without specific prior written 
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

          
A => README.txt +142 -0
@@ 0,0 1,142 @@ 
+fabric-deploy-django
+------------------
+
+Easily reusable fabfile that can be used with the fabric application 
+to quickly deploy a Django project using Apache, mod_wsgi, virtualenv 
+and pip.
+
+
+Dependencies
+------------
+
+You will need to have fabric installed to use this fabfile. Install it
+it like so:
+
+$ sudo pip install fabric
+
+It was written for Python 2.6+
+
+If you use a Python version less than 2.6 then please refer to this 
+URL:
+
+http://docs.fabfile.org/0.9.2/#documentation
+
+You will need to add the following to your fabfile.py:
+
+from __future__ import with_statement
+
+
+Install
+-------
+
+Simply copy the fabfile.py to the root of your project directory and 
+edit the fabfile.py to fit your project.
+
+Project structure should look like so:
+
+myproject/
+  apache/
+    wsgi_handler.py
+    myproject.conf
+  requirements.txt
+  fabfile.py
+  myproject/
+    __init__.py
+    urls.py
+    settings.py
+
+
+Configure
+---------
+
+Edit the following globals:
+
+
+env.project_name = 'PROJECT_NAME'
+
+This is the name the project (ie, "myproject" above in the structure example)
+
+
+env.run_south_migrate = True
+
+Run 'python manage.py migrate' after syncdb is run.
+
+
+env.copy_base_settings_local = False
+
+If true, the script will look for a file named myproject/base_settings_local.py
+and copy it to myproject/settings_local.py
+
+
+env.num_releases = 7
+
+The number of old releases to leave on the deploy destination.
+
+
+Edit the 3 deploy defining functions in the fabfile (local, staging, prod):
+
+
+env.hosts = ['localhost']
+
+Host(s) that the commands should be run on
+
+
+env.path = '/path/to/envs/myproject'
+
+Path for this project to be deployed to.
+
+
+env.user = 'yourusername'
+
+The username to run the commands as
+
+
+env.run_type = 'local'
+
+If this is set to 'local' then the internal function _is_local() will evaluate 
+to True. This means that the deploy is actually being run on the LOCAL machine.
+This is mostly used for testing the deploy process. Anything other than 
+'local' will mean that the deploy will be run on whatever hosts are set in 
+"env.hosts"
+
+
+env.repo_path = '/path/to/local/repo/myproject'
+
+Path (local FS or URL) to the repository to clone (or pull from) during the 
+deploy.
+
+
+env.repo_rev = 'tip'
+
+The revision to use when pulling/cloning from the repository 
+(set in env.repo_path above) 
+
+
+Use
+---
+
+Here is a basic deploy to the LOCAL machine:
+
+$ fab local setup
+
+Or say to the production environment:
+
+$ fab prod setup
+
+
+TODO
+----
+Create a more through install and use docs.
+
+Auto import __future__ if using Python < 2.6
+
+Make various functions more loosely coupled to the tools currently.
+(ie, easy to switch from deploying with hg to git or svn, etc.)
+
+
+Copyright & Warranty
+--------------------
+All documentation, libraries, and sample code are 
+Copyright 2010 Peter Sanchez <petersanchez@gmail.com>. The library and 
+sample code are made available to you under the terms of the BSD license 
+which is contained in the included file, BSD-LICENSE.

          
A => fabfile.py +296 -0
@@ 0,0 1,296 @@ 
+'''
+Original idea from:
+
+http://gist.github.com/571155
+
+Hacked it to basically be completely original.
+
+'''
+import os
+from fabric.api import *
+
+
+# globals
+env.project_name = 'PROJECT_NAME'
+env.run_south_migrate = True
+env.copy_base_settings_local = False
+env.num_releases = 7
+
+
+# Environments
+def _is_local():
+    require('run_type', provided_by=[local, staging, prod])
+    return env.run_type == 'local'
+
+
+def _get_callable(default=run):
+    'Return local or default (run) based on hosts variable'
+    require('run_type', provided_by=[local, staging, prod])
+    def _local(cmd, *args, **kwargs):
+        return local(cmd)
+    return _local if _is_local() else default
+
+
+def _get_cmds():
+    'Return run, cd and sudo commands.'
+    return (
+        _get_callable(),
+        _get_callable(default=sudo),
+    )
+
+
+def localdev():
+    'Use the local machine'
+    env.hosts = ['localhost']
+    env.path = '/path/to/environments/PROJECT_NAME'
+    env.user = 'username'
+    env.run_type = 'local'
+    env.repo_path = '/path/to/local/repository'
+    env.repo_rev = 'tip'
+    env.copy_base_settings_local = True
+
+
+def staging():
+    'Use the staging environment'
+    pass
+
+
+def prod():
+    'Use the production environment'
+    env.hosts = ['yourproject.yourdomain.com']
+    env.path = '/path/to/environments/PROJECT_NAME'
+    env.user = 'username'
+    env.run_type = 'prod'
+    env.repo_path = 'http://bitbucket.org/someuser/yourproject'
+    env.repo_rev = 'tip'
+
+
+def set_repo_revision(rev='tip'):
+    env.repo_rev = rev
+
+
+def set_num_releases(amount=7):
+    env.num_releases = amount
+
+
+# Tasks
+def test():
+    'Run the test suite and bail out if it fails'
+    _run, _sudo = _get_cmds()
+
+    with cd(env.project_name):
+        _run('python manage.py test')
+
+
+def setup():
+    '''
+    Setup a fresh virtualenv as well as a few useful directories, then run
+    a full deployment
+    '''
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    _run, _sudo = _get_cmds()
+    
+    import time
+    env.release = time.strftime('%Y%m%d%H%M%S')
+
+    lpath = '%(path)s/releases/%(release)s' % env
+    _run('mkdir -p %s' % lpath)
+    with cd(lpath):
+        _run('virtualenv --no-site-packages .')
+        _run('mkdir -p shared packages')
+    deploy()
+
+
+def soft_update():
+    'Just update codebase. Do not do a full deploy'
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    
+    checkout_code_repo(update=True)
+    touch_wsgi_handler()
+
+
+def deploy():
+    '''
+    Deploy the latest version of the site to the servers, install any
+    required third party modules, install the virtual host and 
+    then restart the webserver
+    '''
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+
+    checkout_code_repo()
+    install_requirements()
+    migrate()
+    install_site()
+    symlink_current_release()
+    restart_webserver()
+    remove_oldest_release()
+
+
+def show_versions():
+    'List all deployed versions'
+    require('path')
+    _run, _sudo = _get_cmds()
+
+    with cd('%(path)s/releases' % env):
+        print _run('ls -xt')
+
+
+def rollback_version(version):
+    'Specify a specific version to be made live'
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    _run, _sudo = _get_cmds()
+    
+    env.version = version
+    with cd('%(path)s' % env):
+        _run('ln -nfs releases/%(version)s current' % env)
+    restart_webserver()
+
+
+def rollback():
+    'Simple GENERIC rollback. Symlink to the second most recent release'
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    _run, _sudo = _get_cmds()
+
+    releases = _get_releases_list()
+    release = releases[-2]
+    _run('ln -nfs releases/%s current' % release)
+    restart_webserver()
+
+
+def fail_cleanup():
+    '''
+    If your VERY LAST deploy failed, call this to clean up the
+    mess left behind. It will only remove the most recent 
+    release directory. BE CAREFUL USING THIS.
+    '''
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    _run, _sudo = _get_cmds()
+
+    releases = _get_releases_list()
+    release = releases[-1]
+    with cd('%(path)s/releases' % env):
+        _run('rm -rf %s' % release)
+
+
+# Helpers. These are called by other functions rather than directly
+def _get_releases_list():
+    'Returns sorted list of all current releases'
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('path')
+    _run, _sudo = _get_cmds()
+
+    with cd('%(path)s/releases' % env):
+        releases = _run('ls -xt')
+        releases = [x.replace('/', '') for x in releases.split()]
+    return sorted(releases)
+
+
+def checkout_code_repo(update=False):
+    'Create an archive from the current Git master branch and upload it'
+    require('repo_path', provided_by=[localdev, staging, prod])
+    require('repo_rev', provided_by=[localdev, staging, prod])
+    _run, _sudo = _get_cmds()
+
+    if update:
+        lpath = '%(path)s/current/%(project_name)s' % env
+        with cd(lpath):
+            _run('hg pull -r %(repo_rev)s -u' % env)
+    else:
+        require('release', provided_by=[deploy, setup])
+        lpath = '%(path)s/releases/%(release)s' % env
+        with cd(lpath):
+            cmd = 'hg clone -r '
+            cmd += '%(repo_rev)s %(repo_path)s %(project_name)s' % env
+            _run(cmd)
+
+    if env.copy_base_settings_local:
+        lpath = os.path.join(lpath, '%(project_name)s/%(project_name)s' % env)
+        with cd(lpath):
+            with settings(warn_only=True):
+                _run('cp base_settings_local.py settings_local.py')
+
+
+def install_site():
+    'Add the virtualhost file to apache'
+    require('release', provided_by=[deploy, setup])
+    _run, _sudo = _get_cmds()
+
+    if not _is_local():
+        with cd('%(path)s/releases/%(release)s' % env):
+            cmd = 'cp %(project_name)s/apache/%(project_name)s.conf ' % env + \
+                  '/etc/apache2/conf.d/'
+            _sudo(cmd)
+
+
+def install_requirements():
+    'Install the required packages from the requirements file using pip'
+    require('release', provided_by=[deploy, setup])
+    _run, _sudo = _get_cmds()
+
+    with cd('%(path)s/releases/%(release)s' % env):
+        cmd = './bin/pip install -E . '
+        cmd += '-r ./%(project_name)s/requirements.txt' % env
+        _run(cmd)
+
+
+def symlink_current_release():
+    'Symlink our current release'
+    require('release', provided_by=[deploy, setup])
+    _run, _sudo = _get_cmds()
+
+    with cd('%(path)s' % env):
+        _run('ln -nfs releases/%(release)s current' % env)
+
+
+def migrate():
+    'Update the database'
+    require('project_name')
+    require('release', provided_by=[deploy, setup])
+    _run, _sudo = _get_cmds()
+
+    p = '%(path)s/releases/%(release)s/%(project_name)s/%(project_name)s' % env
+    with cd(p):
+        _run('../../bin/python manage.py syncdb --noinput')
+        if env.run_south_migrate:
+            _run('../../bin/python manage.py migrate')
+
+
+def restart_webserver():
+    'Restart the web server'
+    _run, _sudo = _get_cmds()
+
+    if not _is_local():
+        _sudo('service apache2 restart')
+
+
+def touch_wsgi_handler():
+    'Touch the mod_wsgi wsgi_handler.py file'
+    require('project_name')
+    require('hosts', provided_by=[localdev, staging, prod])
+    _run, _sudo = _get_cmds()
+
+    with cd('%(path)s/current/%(project_name)s/apache' % env):
+        _run('touch wsgi_handler.py')
+
+
+def remove_oldest_release():
+    '''
+    Remove oldest releases past the ammount passed 
+    in with the "allow" variable. Default 7.
+    '''
+    require('hosts', provided_by=[localdev, staging, prod])
+    require('num_releases')
+    require('path')
+    _run, _sudo = _get_cmds()
+
+    releases = _get_releases_list()
+    with cd('%(path)s/releases' % env):
+        while len(releases) > env.num_releases:
+            release = releases.pop(0)
+            _run('rm -rf %s' % release)