Skip to content

Instantly share code, notes, and snippets.

@wolever
Created November 11, 2014 20:59
Show Gist options
  • Select an option

  • Save wolever/72d753b114fa7ac16173 to your computer and use it in GitHub Desktop.

Select an option

Save wolever/72d753b114fa7ac16173 to your computer and use it in GitHub Desktop.
"""
Of special interest:
- The ``Target`` class
- The ``Target.confirm_command`` and ``Target.confirm_git_branch`` methods
- The list of target definitions (``targets = [...]``)
- Using the ``.ak-default-target`` file to define a default target (can be different across branches)
Example use: ``fab ak-dev deploy``
"""
import os
from contextlib import nested
from fabric.context_managers import cd, lcd
from fabric.api import local, settings, abort, env, task, run, sudo, cd, prefix
from fabric.colors import red, blue, green, yellow
from fabric.operations import open_shell
from fabric.contrib import console
import requests
rel = lambda *a: os.path.join(os.path.dirname(__file__), *a)
def git_current_branch():
with open(rel(".git/HEAD")) as f:
return f.read().rpartition("/")[2].strip()
def path_property(name, *parts):
def path_prop_helper(self):
return os.path.join(getattr(self, name + "_base"),
getattr(self, name), *parts)
path_prop_helper.__name__ = name
return property(path_prop_helper)
class Target(object):
dir_base = "/data/web/"
virtualenv_path = path_property("virtualenv")
dir_path = path_property("dir")
user = "deploy"
def __init__(self, name, virtualenv=None, dir=None, host=None,
user=None, require_confirmation=False, branch=None):
self.name = name
self.dir = dir
self.host = host or dir
self.user = user or self.user
self.require_confirmation = require_confirmation
self.has_confirmation = False
self.branch = branch
self.branch_confirmed = False
@property
def env_active(self):
return "source %s/activate" %(self.dir_path, )
@property
def repo_path(self):
return os.path.join(self.dir_path, "repo")
def assert_active(self):
pass
def confirm_command(self):
if self.has_confirmation or not self.require_confirmation:
return
expected = self.host
actual = raw_input("Enter the target hostname, %s, to continue: "
%(green(expected),))
if expected != actual:
raise AssertionError(red("%r != %r; aborting."
%(expected, actual)))
self.has_confirmation = True
def confirm_git_branch(self):
if not self.branch or self.branch_confirmed:
return
cur_branch = git_current_branch()
if cur_branch != self.branch:
print "%s: expected branch %s but on branch %s" %(
red("WARNING:"), green(self.branch), red(cur_branch),
)
conf = raw_input("Enter the current branch, %s, to continue: "
%(red(cur_branch), ))
if conf != cur_branch:
raise AssertionError(red("%s != %r; aborting." %(cur_branch, conf)))
self.branch_confirmed = True
def environ(self, path=""):
return nested(
prefix("export DEPLOY_ROOT='%s'" %(self.dir_path, )),
prefix(self.env_active),
cd(os.path.join(self.dir_path, path)),
)
class EmptyTarget(object):
def __nonzero__(self):
return False
def assert_active(self):
raise AssertionError(red("No target set! Set a target with: fab <target> ... cmd"))
def environ(self, *args, **kwargs):
self.assert_active()
def confirm_command(self):
self.assert_active()
targets = {
"ak": Target("ak-dev", dir="dev.akindi.com"),
"ak-prod": Target("ak-prod", dir="akindi.com", require_confirmation=True, branch="master"),
"ak-legacy": Target("ak-legacy", dir="legacy.akindi.com", require_confirmation=True, branch="legacy"),
}
for alias, target in targets.items():
target.alias = alias
@task
def build():
with lcd(rel("akindi/media/ui-angular/")):
local("PROD=1 ./node_modules/.bin/webpack")
@task(aliases=targets.keys())
def target(name=None):
name = name or env.command
target = targets[name]
env.target = target
env.user = target.user
env.hosts = [target.host]
@task(aliases=["bash", "sh"])
def shell():
if not env.target:
open_shell()
return
with env.target.environ():
open_shell(command="cd '%s'; %s" %(env.cwd, " && ".join(env.command_prefixes)))
@task
def status():
filter = env.target and "-%s" %(env.target.host, ) or ""
status = sudo("/usr/bin/supervisorctl status", shell=False)
running = True
for line in status.splitlines():
if filter not in line:
continue
running = running and "RUNNING" in line
color = "RUNNING" in line and green or red
print color(line)
return running
@task
def http_status():
with env.target.environ():
prefix = run("echo $EXTERNAL_URL_PREFIX")
res = requests.get(prefix + "/debug/status")
print res.text
@task
def push(branch="HEAD"):
env.target.confirm_git_branch()
env.target.confirm_command()
env.target.assert_active()
local("git push -f %s %s:incoming" %(env.target.alias, branch, ))
@task
def force():
env["force"] = True
@task
def git_reset():
env.target.confirm_command()
with env.target.environ(path="repo/"):
arg = env.get("force") and "--hard" or "--merge"
run("git checkout deploy")
run("git reset %s incoming" %(arg, ))
@task
def update(checkmigrations=True):
env.target.confirm_command()
with env.target.environ(path="repo/"):
if env.get("force"):
merge_cmd = "reset --hard"
else:
merge_cmd = "merge --ff-only"
run("git checkout deploy")
run("git %s incoming" %(merge_cmd, ))
if checkmigrations:
run("python manage.py checkmigrations || true")
@task
def migrate():
env.target.confirm_command()
with env.target.environ(path="repo/"):
run("./manage.py migrate")
@task
def checkmigrations():
env.target.confirm_command()
with env.target.environ(path="repo/"):
run("./manage.py checkmigrations")
@task
def requirements():
env.target.confirm_command()
with env.target.environ(path="repo/deploy/"):
run("make requirements")
@task
def restart():
env.target.confirm_command()
with env.target.environ(path="repo/deploy/"):
run("make restart")
@task
def restart_workers():
env.target.confirm_command()
with env.target.environ(path="repo/deploy/"):
run("make restart-workers")
@task
def deploy():
env.target.confirm_command()
push()
update(checkmigrations=False)
with env.target.environ(path="repo/deploy/"):
run("make deploy")
http_status()
@task
def tail():
with env.target.environ():
run("cd logs; tail -f $(ls -1t | tac)")
def _load_default_target():
try:
default_target_file = os.path.join(
os.path.dirname(__file__),
".ak-default-target",
)
with open(default_target_file) as f:
target(f.read().strip())
return
except OSError:
pass
env.hosts = ["dev.akindi.com"]
env.user = "deploy"
env.target = EmptyTarget()
env.forward_agent = True
_load_default_target()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment