# -*- coding: utf-8 -*- from __future__ import with_statement, print_function from functools import wraps from os.path import dirname, abspath import sys from fabric.api import * from fabric.context_managers import settings, hide from fabric.decorators import with_settings from fabric.contrib.files import exists from fabric.colors import yellow, red, white, green, blue # blue is used # # GLOBALS # this_module = sys.modules[__name__] env.LOCAL_PATH = dirname(abspath(__file__)) env.hosts = ['localhost', 'tristram.fmartingr.com'] repository = 'https://gitlab.com/fmartingr/fmartingr.com.git' deploy_to = '/var/www/fmartingr.com' deploy_user = 'fmartingrcom' app_name = 'fmartingrcom' app_configfile = '{}/shared/config/config.toml'.format(deploy_to) django_settings = 'fmartingrcom.settings.configfile' managepy_affix = '--settings={}'.format(django_settings) # Doctor checkups DOCTOR = { 'apps': ['virtualenv', 'python', 'npm', 'grunt'] } # # CONTEXT MANAGERS # def virtualenv(): """ Activates virtualenv first """ return prefix('source {}/.virtualenv/bin/activate'.format(env.LOCAL_PATH)) # # CUSTOM DECORATORS # def as_user(user): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): with settings(user=user): return func(*args, **kwargs) return wrapper return decorator # # OUTPUT # def header(string, color='yellow'): color_cmd = getattr(this_module, color) print('{} {}'.format(color_cmd('----->'), string)) def subheader(string, color='green'): color_cmd = getattr(this_module, color) print(' {} {}'.format(color_cmd('-'), string)) def subtext(string, color='white'): color_cmd = getattr(this_module, color) print(' {}'.format(color_cmd(string))) # # TASKS # @task def setup_environment(): """ Prepares environment for the application """ with cd(env.LOCAL_PATH): execute(setup_virtualenv) execute(setup_tools) execute(setup_database) @task def setup_virtualenv(appenv='local'): """ Creates or updates a virtualenv """ if not exists('.virtualenv'): print(yellow('Create virtualenv')) run('virtualenv .virtualenv') with virtualenv(): print(yellow('Installing requirements')) run('pip install -r requirements-{}.txt'.format(appenv)) @task def setup_tools(): # Setup frontend tools print(yellow('Installing npm dependencies')) run('npm install') print(yellow('Installing bower dependencies')) run('bower install') @task def setup_database(): """ Create or update the database """ with virtualenv(): print(yellow('SyncDB')) run('python manage.py syncdb') print(yellow('Migrate')) run('python manage.py migrate') @task def doctor(): print(yellow('Checking for software:')) for app in DOCTOR['apps']: print(white('{}'.format(app)), end=': ') check = run('which {}'.format(app), quiet=True) if check.succeeded: print(green('present')) else: print(red('not present')) # # LOCAL ONLY # @task @hosts(['localhost']) def runserver(): """ Executes local development server """ with cd(env.LOCAL_PATH): with virtualenv(): run('python manage.py runserver 0.0.0.0:8000') @task @hosts(['localhost']) def rungrunt(): """ Executes grunt """ with cd(env.LOCAL_PATH): run('grunt watch') # # DEPLOY # # @contextmanager # def allow_rollback(): # try: # yield # except SystemExit: # rollback() # abort("Fail!") @task @hosts(['tristram.fmartingr.com']) @as_user(deploy_user) @with_settings(hide('warnings', 'running', 'stdout')) def deploy(): last_release = int(run('cat {}/releases/_status'.format(deploy_to))) current_release = last_release + 1 release_dir = '{}/releases/{}'.format(deploy_to, current_release) header('Preparing for release v{}'.format(current_release)) # If release dir exists the last build failed with settings(warn_only=True): if not run("test -d {}".format(release_dir)).failed: # Remove the build subheader('Last build seems to have failed', 'blue') run("rm -rf {}".format(release_dir)) subheader('Last build directory removed') run('mkdir {}'.format(release_dir)) code_dir = '{}/code'.format(deploy_to) header('Updating source code from cvs') with settings(warn_only=True): if run("test -d {}".format(code_dir)).failed: cmd = "git clone {} {}".format(repository, code_dir) subheader(cmd) run(cmd) with cd(code_dir): subheader("git pull") run("git pull") header('Copying code to release') cmd = 'cp -R {}/code {}/code'.format(deploy_to, release_dir) subheader(cmd) run(cmd) header('Setting up release environment') with cd(release_dir): cmd = 'virtualenv virtualenv' subheader(cmd) run(cmd) with prefix('source {}/virtualenv/bin/activate'.format(release_dir)): cmd = 'pip install -r code/requirements-prod.txt' subheader(cmd) run(cmd) header('Linking shared files') cmd = 'ln -s {}/shared {}/shared'.format(deploy_to, release_dir) subheader(cmd) run(cmd) header('Prepare database') with cd(release_dir): with prefix('source {}/virtualenv/bin/activate'.format(release_dir)): with prefix('export APP_CONFIGFILE="{}"'.format(app_configfile)): cmd = 'python code/manage.py migrate {} --no-input'\ .format(managepy_affix) subheader(cmd) run(cmd) header('Download and move staticfiles') with cd('{}/code'.format(release_dir)): cmd = 'bower install' subheader(cmd) run(cmd) with prefix('source {}/virtualenv/bin/activate'.format(release_dir)): with prefix('export APP_CONFIGFILE="{}"'.format(app_configfile)): cmd = 'python manage.py collectstatic --c --noinput {}'\ .format(managepy_affix) subheader(cmd) run(cmd) # Symlink vX to current header('Activate current release') cmd = 'ln -sfn {}/ {}/current'.format(release_dir, deploy_to) subheader(cmd) run(cmd) # Restart supervisorctl header('Restart') cmd = 'sudo supervisorctl restart {}'.format(app_name) subheader(cmd) run(cmd) # Check if supervisor started ok! header('Checking app restart status') cmd = 'sleep 1' run(cmd) subheader(cmd, color='blue') cmd = 'sudo supervisorctl status fmartingrcom' subheader(cmd) status = run(cmd) if 'RUNNING' in status: subheader('App seems to be running.') else: subheader('App may not be running!', color='red') subtext(' '.join(status.split()), color='red') subtext(run('tail ./shared/log/gunicorn_supervisor.log'), color='red') # Increment version number run('echo {} > {}/releases/_status'.format(current_release, deploy_to)) header(green('Deploy v{} finished!'.format(current_release))) @task @hosts(['tristram.fmartingr.com']) @as_user(deploy_user) @with_settings(hide('warnings', 'running', 'stdout')) def restart(): header('Restart') cmd = 'sudo supervisorctl restart {}'.format(app_name) subheader(cmd) run(cmd) header('Checking app restart status') cmd = 'sleep 1' run(cmd) subheader(cmd, color='blue') cmd = 'sudo supervisorctl status fmartingrcom' subheader(cmd) status = run(cmd) subtext(' '.join(status.split()), color='blue')