Last active
December 16, 2015 14:29
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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__ = "[email protected]" | |
__credits__ = ["https://github.com/fengli"] | |
""" | |
Server roles | |
""" | |
env.roledefs = { | |
'development': ['localhost'], | |
'staging': ['steves-blackbox'], | |
'production': ['[email protected]'], | |
'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