Created
November 11, 2014 20:59
-
-
Save wolever/72d753b114fa7ac16173 to your computer and use it in GitHub Desktop.
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
| """ | |
| 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