piku/piku.py

172 wiersze
5.7 KiB
Python
Czysty Zwykły widok Historia

2016-03-26 12:52:54 +00:00
#!/usr/bin/env python
2016-03-26 22:47:39 +00:00
import os, sys, stat, re, shutil, socket, subprocess
2016-03-26 23:01:28 +00:00
from click import argument, command, group, option, secho as echo
2016-03-27 12:08:30 +00:00
from os.path import abspath, exists, join, dirname
2016-03-26 12:52:54 +00:00
2016-03-27 12:08:30 +00:00
PIKU_ROOT = os.environ.get('PIKU_ROOT', join(os.environ['HOME'],'.piku'))
APP_ROOT = abspath(join(PIKU_ROOT, "apps"))
GIT_ROOT = abspath(join(PIKU_ROOT, "repos"))
UWSGI_ENABLED = abspath(join(PIKU_ROOT, "uwsgi-enabled"))
UWSGI_AVAILABLE = abspath(join(PIKU_ROOT, "uwsgi-available"))
LOG_ROOT = abspath(join(PIKU_ROOT, "logs"))
2016-03-26 12:52:54 +00:00
# http://off-the-stack.moorman.nu/2013-11-23-how-dokku-works.html
2016-03-26 21:33:02 +00:00
def sanitize_app_name(app):
2016-03-26 17:14:13 +00:00
"""Sanitize the app name and build matching path"""
app = "".join(c for c in app if c.isalnum() or c in ('.','_')).rstrip()
2016-03-26 21:33:02 +00:00
return app
2016-03-26 17:14:13 +00:00
2016-03-26 12:52:54 +00:00
def get_free_port(address=""):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((address,0))
port = s.getsockname()[1]
s.close()
return port
2016-03-26 17:28:01 +00:00
2016-03-26 12:52:54 +00:00
def setup_authorized_keys(ssh_fingerprint, script_path, pubkey):
2016-03-26 17:39:23 +00:00
"""Sets up an authorized_keys file to redirect SSH commands"""
2016-03-27 12:08:30 +00:00
authorized_keys = join(os.environ['HOME'],'.ssh','authorized_keys')
if not exists(os.dirname(authorized_keys)):
2016-03-26 12:52:54 +00:00
os.makedirs(os.dirname(authorized_keys))
2016-03-26 17:39:23 +00:00
# Restrict features and force all SSH commands to go through our script
2016-03-26 12:52:54 +00:00
h = open(authorized_keys, 'a')
2016-03-26 17:28:01 +00:00
h.write("""command="FINGERPRINT=%(ssh_fingerprint)s NAME=default %(script_path)s $SSH_ORIGINAL_COMMAND",no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding %(pubkey)s\n""" % locals())
2016-03-26 12:52:54 +00:00
h.close()
2016-03-27 12:08:30 +00:00
@group()
2016-03-26 17:28:01 +00:00
def piku():
2016-03-26 12:52:54 +00:00
pass
2016-03-26 22:47:39 +00:00
2016-03-26 12:52:54 +00:00
2016-03-26 17:28:01 +00:00
@piku.resultcallback()
2016-03-26 12:52:54 +00:00
def cleanup(ctx):
2016-03-26 17:33:14 +00:00
"""Callback from command execution -- currently used for debugging"""
2016-03-26 12:52:54 +00:00
print sys.argv[1:]
2016-03-26 21:33:02 +00:00
#print os.environ
2016-03-26 12:52:54 +00:00
2016-03-26 22:47:39 +00:00
2016-03-26 12:52:54 +00:00
# https://github.com/dokku/dokku/blob/master/plugins/git/commands#L103
2016-03-26 17:28:01 +00:00
@piku.command("git-receive-pack")
2016-03-26 12:52:54 +00:00
@argument('app')
def receive(app):
2016-03-26 23:10:14 +00:00
"""Handle git pushes for an app"""
2016-03-26 21:33:02 +00:00
app = sanitize_app_name(app)
2016-03-27 12:08:30 +00:00
hook_path = join(GIT_ROOT, app, 'hooks', 'post-receive')
if not exists(hook_path):
os.makedirs(dirname(hook_path))
2016-03-26 12:52:54 +00:00
# Initialize the repository with a hook to this script
2016-03-26 22:08:10 +00:00
subprocess.call("git init --quiet --bare " + app, cwd=GIT_ROOT, shell=True)
2016-03-26 12:52:54 +00:00
h = open(hook_path,'w')
h.write("""#!/usr/bin/env bash
set -e; set -o pipefail;
2016-03-26 21:33:02 +00:00
cat | PIKU_ROOT="%s" $HOME/piku.py git-hook %s""" % (PIKU_ROOT, app))
2016-03-26 12:52:54 +00:00
h.close()
2016-03-26 17:34:51 +00:00
# Make the hook executable by our user
os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR)
2016-03-26 12:52:54 +00:00
# Handle the actual receive. We'll be called with 'git-hook' while it happens
2016-03-26 22:08:10 +00:00
subprocess.call('git-shell -c "%s"' % " ".join(sys.argv[1:]), cwd=GIT_ROOT, shell=True)
2016-03-26 12:52:54 +00:00
2016-03-26 22:08:10 +00:00
@piku.command("deploy")
@argument('app')
def deploy_app(app):
2016-03-26 23:01:28 +00:00
"""Deploy an application"""
2016-03-26 22:08:10 +00:00
app = sanitize_app_name(app)
2016-03-26 22:19:30 +00:00
do_deploy(app)
2016-03-26 22:47:39 +00:00
@piku.command("ls")
def list_apps():
2016-03-26 23:01:28 +00:00
"""List applications"""
2016-03-26 22:47:39 +00:00
for a in os.listdir(APP_ROOT):
2016-03-26 23:01:28 +00:00
echo(a, fg='green')
2016-03-26 22:47:39 +00:00
2016-03-27 12:08:30 +00:00
@piku.command("disable")
@argument('app')
def disable_app(app):
"""Disable an application"""
app = sanitize_app_name(app)
config = join(UWSGI_ENABLED, app + '.ini')
if exists(config):
echo("Disabling app '%s'..." % app, fg='yellow')
os.remove(config)
@piku.command("enable")
@argument('app')
def enable_app(app):
"""Enable an application"""
app = sanitize_app_name(app)
live_config = join(UWSGI_ENABLED, app + '.ini')
config = join(UWSGI_AVAILABLE, app + '.ini')
if exists(join(APP_ROOT, app)):
if not exists(live_config):
if exists(config):
echo("Enabling app '%s'..." % app, fg='yellow')
shutil.copyfile(config, live_config)
else:
echo("Error: app '%s' is not configured.", fg='red')
else:
echo("Warning: app '%s' is already enabled, skipping.", fg='yellow')
else:
echo("Error: app '%s' does not exist.", fg='red')
2016-03-26 22:47:39 +00:00
@piku.command("destroy")
@argument('app')
def destroy_app(app):
2016-03-26 23:01:28 +00:00
"""Destroy an application"""
2016-03-26 22:47:39 +00:00
app = sanitize_app_name(app)
2016-03-27 12:08:30 +00:00
paths = [join(x, app) for x in [APP_ROOT, GIT_ROOT, LOG_ROOT]]
2016-03-26 22:47:39 +00:00
for p in paths:
2016-03-27 12:08:30 +00:00
if exists(p):
echo("Removing folder '%s'" % p, fg='yellow')
2016-03-26 23:01:28 +00:00
shutil.rmtree(p)
2016-03-27 12:08:30 +00:00
for p in [join(x, app + '.ini') for x in [UWSGI_AVAILABLE, UWSGI_ENABLED]]
if exists(p):
echo("Removing file '%s'" % p, fg='yellow')
2016-03-27 11:54:25 +00:00
os.remove(p)
2016-03-26 22:47:39 +00:00
2016-03-26 22:19:30 +00:00
def do_deploy(app):
2016-03-26 22:47:39 +00:00
"""Deploy an app by resetting the work directory"""
2016-03-27 12:08:30 +00:00
app_path = join(APP_ROOT, app)
env = {'GIT_WORK_DIR': app_path}
if exists(app_path):
echo("-----> Deploying app '%s'" % app, fg='green')
2016-03-26 22:37:11 +00:00
subprocess.call('git pull --quiet', cwd=app_path, env=env, shell=True)
subprocess.call('git checkout -f', cwd=app_path, env=env, shell=True)
2016-03-26 22:08:10 +00:00
else:
2016-03-27 12:08:30 +00:00
echo("Error: app '%s' not found." % app, fg='red')
2016-03-26 22:08:10 +00:00
2016-03-26 17:28:01 +00:00
@piku.command("git-hook")
2016-03-26 12:52:54 +00:00
@argument('app')
def git_hook(app):
2016-03-26 21:33:02 +00:00
"""Post-receive git hook"""
app = sanitize_app_name(app)
2016-03-27 12:08:30 +00:00
repo_path = join(GIT_ROOT, app)
app_path = join(APP_ROOT, app)
2016-03-26 17:14:13 +00:00
for line in sys.stdin:
2016-03-26 17:15:19 +00:00
oldrev, newrev, refname = line.strip().split(" ")
2016-03-26 22:08:10 +00:00
#print "refs:", oldrev, newrev, refname
2016-03-26 17:14:13 +00:00
if refname == "refs/heads/master":
2016-03-26 17:23:29 +00:00
# Handle pushes to master branch
2016-03-27 12:08:30 +00:00
if not exists(app_path):
echo("-----> Creating app '%s'" % app, fg='green')
2016-03-26 21:33:02 +00:00
os.makedirs(app_path)
2016-03-26 22:37:11 +00:00
subprocess.call('git clone --quiet %s %s' % (repo_path, app), cwd=APP_ROOT, shell=True)
2016-03-26 22:19:30 +00:00
do_deploy(app)
2016-03-26 17:14:13 +00:00
else:
2016-03-26 17:23:29 +00:00
# Handle pushes to another branch
2016-03-26 17:28:01 +00:00
print "receive-branch", app, newrev, refname
2016-03-26 21:33:02 +00:00
print "hook", app, sys.argv[1:]
2016-03-26 12:52:54 +00:00
if __name__ == '__main__':
2016-03-26 17:28:01 +00:00
piku()