Skip to content

Instantly share code, notes, and snippets.

@SteveRyherd
Last active December 16, 2015 14:29
Show Gist options
  • Save SteveRyherd/5449200 to your computer and use it in GitHub Desktop.
Save SteveRyherd/5449200 to your computer and use it in GitHub Desktop.
Generic fabfile for deploying small projects. Commands are setup for an Linux, Apache, Git environment but can easily be changed or extended for other scenarios.
"""
Generic fabfile.py template for deploying to remote servers.
This fabfile is designed to be used as a template when working in environments
where the production server may not have access to the primary 'repository' or
'staging' server. For example, a small develop may lack a dedicated development
or staging server and lack a static-ip or permanent address.
Instead of the production server pulling changes from the remote machine, changes
are pushed to a bare repository on the server and checked out from there.
Setup:
===
Change the ``env.project`` variable to a unique project name. This variable
is used to define the directories for deployment and your repository name.
Server configuration files are stored in a subdirectory per server. The server
settings to use are currently hardcoded into ``deploy()`` and use:
- /apache/staging.conf
- /apache/production.conf
The master repository is defined in the roledefs. It is ok to use your staging
or development server repository.
The two main commands to use are:
- new_project
Creates deployment repositories on all servers and turns your working
directory into a repository.
- deploy
Deploys your project to the selected server. Defaults to ``staging``
Special Notes:
===
This fabfile template is purposely missing the following features for simplicity:
- support for multiple server types
- downloading project requirements
- setting up databases
- deploying content to content distribution networks.
- shortcuts for commmits/rollbacks/pushing to servers.
"""
from __future__ import with_statement
from fabric.api import *
from fabric.contrib import files
__author__ = "Steve Ryherd"
__email__ = "steve@desatt.com"
__credits__ = ["https://github.com/fengli"]
"""
Server roles
"""
env.roledefs = {
'development': ['localhost'],
'staging': ['steves-blackbox'],
'production': ['root@desatt.com'],
'git': ['steves-blackbox'],
}
"""
Base configuration
"""
env.project = 'projectName'
env.web_server = 'apache'
env.repo_dir = '/srv/git/' + env.project + '.git'
env.web_dir = '/var/www/vhosts/' + env.project
env.master_repo = env.roledefs['git'][0] + ':' + env.repo_dir
"""
Repository Setup
---
Used for the initial creation of the Master repository and remote repositories
on the servers to be deployed to.
"""
@task
def new_project():
"""
Creates a master repository on the GIT server.
Bare repositories in staging/production
"""
# Only do this if the master repo doesn't exist.
# Otherwise checkout the development branch...
execute(create_master_repo)
execute(create_remote_repos)
execute(update_hosts)
create_development_repo()
@roles('development')
def create_development_repo():
"""
Creates a repository in the current directory with a development branch.
Adds the master repository as it's origin.
"""
local("git init")
local("git remote add origin %(master_repo)s" % env)
local("git checkout -b development")
local("touch .gitignore")
local("git add .gitignore")
local("git commit -am \"Creating initial repository with 'development' branch\"")
local("git push origin development")
def create_bare_repo(location):
"""
Creates a bare git repository. This is used to push changes upward,
because of the challenges of pulling from a dynamic remote repo.
"""
if not files.exists(location):
run('mkdir ' + location)
with cd(location):
run('git init --bare')
else:
warn("Bare repository directory already exists. Skipping Init.")
@roles('git')
def create_master_repo():
"""
Create a 'Central' server to manage pushing updates to remote clients.
"""
create_bare_repo(env.repo_dir)
@roles('staging', 'production')
def create_remote_repos():
"""
Because we need to PUSH to our external servers we need to have bare repositories
on them
"""
create_bare_repo(env.repo_dir)
"""
Deployment
"""
@task(default=True)
def deploy(environment='staging', branch=None):
""" Deploys project on the server """
# Requires settings and branch to deploy.
# Staging should always be staging/development
# Production should always be production/master
env.hosts = env.roledefs[environment]
if environment == 'production':
env.branch = 'master'
env.settings = 'production'
else:
env.branch = 'development' if branch is None else branch
env.settings = 'staging'
print "Deploying", env.branch, "branch to", environment, "servers."
# @TODO Add option to push local repository to master?
execute(push_to_remotes, environment) # Pushes all updates to selected remotes
execute(checkout_latest) # checkout repository
execute(configure_web_server)
return
@roles('git')
def push_to_remotes(environment='staging'):
"""
Pushes all updates from the master repository to the remote repos.
"""
with cd(env.repo_dir):
for hostname in env.roledefs[environment]:
print 'Pushing updates to', hostname
run('git push %s:%s --all' % (hostname, env.repo_dir))
def checkout_latest():
if not files.exists(env.web_dir):
run("git clone -b %(branch)s %(repo_dir)s %(web_dir)s" % env)
else:
with cd(env.web_dir):
# Does NOT overwrite untracked files;
# /static/ or /uploads/ should be safe.
run("git checkout %(branch)s" % env)
run("git pull origin %(branch)s" % env)
"""
Apache Tasks
"""
@task
def configure_web_server():
""" upload/prepare web server config """
src = '%(web_dir)s/%(web_server)s/%(settings)s.conf' % env
dest = '/etc/apache2/sites-available/%(project)s.conf' % env
files.upload_template(src, dest, use_sudo=True)
enable_site()
@task
def enable_site():
""" Enables website on server """
sudo('a2ensite %(project)s.conf' % env)
restart_server()
@task
def disable_site():
""" Disables website on server """
sudo('a2dissite %(project)s.conf' % env)
restart_server()
@task
def restart_server():
""" Restart server """
sudo('service apache2 restart')
"""
Development Extras
"""
@task
@roles('development')
def update_hosts():
""" Adds project.localhost to host file """
if not files.contains("/etc/hosts", "%(project)s.localhost" % env):
print 'Adding %(project)s.localhost to /etc/hosts file' % env
files.append('/etc/hosts', '127.0.0.1 %(project)s.localhost' % env, use_sudo=True)
else:
print "Host file is up to date."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment