realms-wiki/realms/commands.py

462 lines
11 KiB
Python
Raw Permalink Normal View History

from __future__ import absolute_import
2014-10-17 00:54:45 +03:00
import json
import sys
import os
2014-10-24 02:58:58 +03:00
import time
2014-11-11 22:48:11 +02:00
import subprocess
from subprocess import call, Popen
from multiprocessing import cpu_count
import click
import pip
2016-08-20 20:37:22 +03:00
from realms import config, create_app, db, __version__, cli, cache
from realms.lib.util import random_string, in_virtualenv, green, yellow, red
2014-10-17 00:54:45 +03:00
2016-09-05 10:37:59 +03:00
config = config.conf
2016-09-05 10:37:59 +03:00
2014-10-24 02:58:58 +03:00
# called to discover commands in modules
app = create_app()
2014-10-22 00:06:27 +03:00
2016-04-25 02:11:47 +03:00
2014-10-17 00:54:45 +03:00
def get_user():
for name in ('SUDO_USER', 'LOGNAME', 'USER', 'LNAME', 'USERNAME'):
user = os.environ.get(name)
if user:
return user
def get_pid():
try:
with file(config.PIDFILE) as f:
2014-10-24 02:58:58 +03:00
return f.read().strip()
2014-10-17 00:54:45 +03:00
except IOError:
2014-10-24 02:58:58 +03:00
return None
def is_running(pid):
if not pid:
return False
pid = int(pid)
try:
os.kill(pid, 0)
except OSError:
2014-10-17 00:54:45 +03:00
return False
2014-10-24 02:58:58 +03:00
return True
2014-10-17 00:54:45 +03:00
def module_exists(module_name):
try:
__import__(module_name)
except ImportError:
return False
else:
return True
def prompt_and_invoke(ctx, fn):
# This is a workaround for a bug in click.
# See https://github.com/mitsuhiko/click/issues/429
# This isn't perfect - we are ignoring some information (type mostly)
kw = {}
for p in fn.params:
v = click.prompt(p.prompt, p.default, p.hide_input,
p.confirmation_prompt, p.type)
kw[p.name] = v
ctx.invoke(fn, **kw)
2014-10-17 00:54:45 +03:00
@cli.command()
@click.option('--site-title',
default=config.SITE_TITLE,
prompt='Enter site title.')
@click.option('--base_url',
default=config.BASE_URL,
prompt='Enter base URL.')
@click.option('--port',
default=config.PORT,
prompt='Enter port number.')
@click.option('--secret-key',
default=config.SECRET_KEY if config.SECRET_KEY != "CHANGE_ME" else random_string(64),
prompt='Enter secret key.')
@click.option('--wiki-path',
default=config.WIKI_PATH,
2014-10-17 06:18:57 +03:00
prompt='Enter wiki data directory.',
2014-10-17 00:54:45 +03:00
help='Wiki Directory (git repo)')
@click.option('--allow-anon',
default=config.ALLOW_ANON,
is_flag=True,
prompt='Allow anonymous edits?')
@click.option('--registration-enabled',
default=config.REGISTRATION_ENABLED,
is_flag=True,
prompt='Enable registration?')
@click.option('--cache-type',
default=config.CACHE_TYPE,
type=click.Choice([None, 'simple', 'redis', 'memcached']),
prompt='Cache type?')
@click.option('--search-type',
default=config.SEARCH_TYPE,
type=click.Choice(['simple', 'whoosh', 'elasticsearch']),
prompt='Search type?')
2014-10-17 00:54:45 +03:00
@click.option('--db-uri',
default=config.DB_URI,
prompt='Database URI? Examples: http://goo.gl/RyW0cl')
@click.pass_context
def setup(ctx, **kw):
""" Start setup wizard
"""
2014-10-24 02:58:58 +03:00
try:
os.mkdir('/etc/realms-wiki')
except OSError:
pass
2014-10-17 00:54:45 +03:00
conf = {}
for k, v in kw.items():
conf[k.upper()] = v
conf_path = config.update(conf)
if conf['CACHE_TYPE'] == 'redis':
prompt_and_invoke(ctx, setup_redis)
2014-10-17 00:54:45 +03:00
elif conf['CACHE_TYPE'] == 'memcached':
prompt_and_invoke(ctx, setup_memcached)
2014-10-17 00:54:45 +03:00
if conf['SEARCH_TYPE'] == 'elasticsearch':
prompt_and_invoke(ctx, setup_elasticsearch)
elif conf['SEARCH_TYPE'] == 'whoosh':
install_whoosh()
2014-10-17 06:18:57 +03:00
green('Config saved to %s' % conf_path)
2014-10-23 01:16:55 +03:00
if not conf_path.startswith('/etc/realms-wiki'):
yellow('Note: You can move file to /etc/realms-wiki/realms-wiki.json')
2014-10-23 01:16:55 +03:00
click.echo()
2014-10-17 06:18:57 +03:00
yellow('Type "realms-wiki start" to start server')
yellow('Type "realms-wiki dev" to start server in development mode')
2014-10-23 01:16:55 +03:00
yellow('Full usage: realms-wiki --help')
2014-10-17 00:54:45 +03:00
2014-10-24 02:58:58 +03:00
2014-10-17 00:54:45 +03:00
@click.command()
@click.option('--cache-redis-host',
default=getattr(config, 'CACHE_REDIS_HOST', "127.0.0.1"),
prompt='Redis host')
@click.option('--cache-redis-port',
default=getattr(config, 'CACHE_REDIS_POST', 6379),
prompt='Redis port',
type=int)
2014-10-17 00:54:45 +03:00
@click.option('--cache-redis-password',
default=getattr(config, 'CACHE_REDIS_PASSWORD', None),
prompt='Redis password')
@click.option('--cache-redis-db',
default=getattr(config, 'CACHE_REDIS_DB', 0),
prompt='Redis db')
@click.pass_context
def setup_redis(ctx, **kw):
conf = config.read()
2014-10-17 00:54:45 +03:00
for k, v in kw.items():
conf[k.upper()] = v
config.update(conf)
install_redis()
2016-04-25 02:11:47 +03:00
@click.command()
@click.option('--elasticsearch-url',
default=getattr(config, 'ELASTICSEARCH_URL', 'http://127.0.0.1:9200'),
prompt='Elasticsearch URL')
def setup_elasticsearch(**kw):
conf = config.read()
for k, v in kw.items():
conf[k.upper()] = v
config.update(conf)
cli.add_command(setup_redis)
cli.add_command(setup_elasticsearch)
2014-10-17 00:54:45 +03:00
def get_prefix():
return sys.prefix
@cli.command(name='pip')
2014-10-17 00:54:45 +03:00
@click.argument('cmd', nargs=-1)
def pip_(cmd):
2014-10-17 00:54:45 +03:00
""" Execute pip commands, useful for virtualenvs
"""
pip.main(cmd)
2014-10-17 00:54:45 +03:00
def install_redis():
pip.main(['install', 'redis'])
2014-10-17 00:54:45 +03:00
def install_whoosh():
pip.main(['install', 'Whoosh'])
2014-10-17 00:54:45 +03:00
def install_mysql():
pip.main(['install', 'MySQL-Python'])
2014-10-17 00:54:45 +03:00
def install_postgres():
pip.main(['install', 'psycopg2'])
2014-10-17 00:54:45 +03:00
2014-10-24 02:58:58 +03:00
def install_crate():
pip.main(['install', 'crate'])
2014-10-17 00:54:45 +03:00
def install_memcached():
pip.main(['install', 'python-memcached'])
2014-10-17 00:54:45 +03:00
@click.command()
@click.option('--cache-memcached-servers',
default=getattr(config, 'CACHE_MEMCACHED_SERVERS', ["127.0.0.1:11211"]),
type=click.STRING,
prompt='Memcached servers, separate with a space')
def setup_memcached(**kw):
conf = {}
for k, v in kw.items():
conf[k.upper()] = v
config.update(conf)
@cli.command()
@click.option('--user',
default=get_user(),
type=click.STRING,
prompt='Run as which user? (it must exist)')
@click.option('--port',
default=config.PORT,
type=click.INT,
prompt='What port to listen on?')
@click.option('--workers',
default=cpu_count() * 2 + 1,
type=click.INT,
prompt="Number of workers? (defaults to ncpu*2+1)")
def setup_upstart(**kwargs):
""" Start upstart conf creation wizard
"""
from realms.lib.util import upstart_script
if in_virtualenv():
app_dir = get_prefix()
path = '/'.join(sys.executable.split('/')[:-1])
else:
# Assumed root install, not sure if this matters?
app_dir = '/'
path = None
kwargs.update(dict(app_dir=app_dir, path=path))
conf_file = '/etc/init/realms-wiki.conf'
2014-10-23 01:16:55 +03:00
script = upstart_script(**kwargs)
2014-10-17 00:54:45 +03:00
2014-10-23 01:16:55 +03:00
try:
with open(conf_file, 'w') as f:
f.write(script)
green('Wrote file to %s' % conf_file)
except IOError:
with open('/tmp/realms-wiki.conf', 'w') as f:
f.write(script)
yellow("Wrote file to /tmp/realms-wiki.conf, to install type:")
yellow("sudo mv /tmp/realms-wiki.conf /etc/init/realms-wiki.conf")
click.echo()
click.echo("Upstart usage:")
green("sudo start realms-wiki")
green("sudo stop realms-wiki")
green("sudo restart realms-wiki")
green("sudo status realms-wiki")
2014-10-17 00:54:45 +03:00
@cli.command()
@click.argument('json_string')
def configure(json_string):
2014-10-24 02:58:58 +03:00
""" Set config, expects JSON encoded string
2014-10-17 00:54:45 +03:00
"""
try:
config.update(json.loads(json_string))
except ValueError, e:
2014-10-17 06:18:57 +03:00
red('Config value should be valid JSON')
2014-10-17 00:54:45 +03:00
@cli.command()
2014-10-24 02:58:58 +03:00
@click.option('--port', default=config.PORT)
@click.option('--host', default=config.HOST)
def dev(port, host):
2014-10-17 00:54:45 +03:00
""" Run development server
"""
2014-10-17 06:18:57 +03:00
green("Starting development server")
2014-10-24 02:58:58 +03:00
config_path = config.get_path()
if config_path:
green("Using config: %s" % config_path)
else:
yellow("Using default configuration")
create_app().run(host=host,
port=port,
debug=True)
2014-10-17 00:54:45 +03:00
def start_server():
2014-10-24 02:58:58 +03:00
if is_running(get_pid()):
2014-10-17 06:18:57 +03:00
yellow("Server is already running")
2014-10-17 00:54:45 +03:00
return
try:
open(config.PIDFILE, 'w')
except IOError:
red("PID file not writeable (%s) " % config.PIDFILE)
return
2014-10-17 00:54:45 +03:00
flags = '--daemon --pid %s' % config.PIDFILE
2014-10-17 06:18:57 +03:00
green("Server started. Port: %s" % config.PORT)
2014-10-17 00:54:45 +03:00
2014-10-24 02:58:58 +03:00
config_path = config.get_path()
if config_path:
green("Using config: %s" % config_path)
else:
yellow("Using default configuration")
prefix = ''
if in_virtualenv():
prefix = get_prefix() + "/bin/"
Popen("%sgunicorn 'realms:create_app()' -b %s:%s -k gevent %s" %
2016-01-24 18:07:05 +02:00
(prefix, config.HOST, config.PORT, flags), shell=True, executable='/bin/bash')
2014-10-17 00:54:45 +03:00
def stop_server():
pid = get_pid()
2014-10-24 02:58:58 +03:00
if not is_running(pid):
2014-10-17 06:18:57 +03:00
yellow("Server is not running")
2014-10-17 00:54:45 +03:00
else:
2014-10-17 06:18:57 +03:00
yellow("Shutting down server")
2014-10-17 00:54:45 +03:00
call(['kill', pid])
2014-10-24 02:58:58 +03:00
while is_running(pid):
time.sleep(1)
2014-10-17 00:54:45 +03:00
@cli.command()
def run():
""" Run production server (alias for start)
"""
start_server()
@cli.command()
def start():
""" Run server daemon
"""
start_server()
@cli.command()
def stop():
""" Stop server
"""
stop_server()
@cli.command()
def restart():
""" Restart server
"""
stop_server()
start_server()
@cli.command()
def status():
""" Get server status
"""
pid = get_pid()
if not is_running(pid):
2014-10-17 06:18:57 +03:00
yellow("Server is not running")
2014-10-17 00:54:45 +03:00
else:
2014-10-17 06:18:57 +03:00
green("Server is running PID: %s" % pid)
2014-10-17 00:54:45 +03:00
@cli.command()
def create_db():
""" Creates DB tables
"""
2014-10-17 06:18:57 +03:00
green("Creating all tables")
2014-10-31 00:59:19 +02:00
with app.app_context():
green('DB_URI: %s' % app.config.get('DB_URI'))
db.metadata.create_all(db.get_engine(app))
2014-10-17 00:54:45 +03:00
@cli.command()
@click.confirmation_option(help='Are you sure you want to drop the db?')
def drop_db():
""" Drops DB tables
"""
2014-10-17 06:18:57 +03:00
yellow("Dropping all tables")
2014-10-31 00:59:19 +02:00
with app.app_context():
db.metadata.drop_all(db.get_engine(app))
2014-10-17 00:54:45 +03:00
2015-12-20 20:38:19 +02:00
@cli.command()
def clear_cache():
""" Clears cache
"""
yellow("Clearing the cache")
with app.app_context():
cache.clear()
2014-10-17 00:54:45 +03:00
@cli.command()
def test():
""" Run tests
"""
for mod in [('flask_testing', 'Flask-Testing'), ('nose', 'nose'), ('blinker', 'blinker')]:
2014-10-17 00:54:45 +03:00
if not module_exists(mod[0]):
pip.main(['install', mod[1]])
2014-10-17 00:54:45 +03:00
nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests"
call([nosetests, 'realms'])
2014-10-17 00:54:45 +03:00
@cli.command()
def version():
""" Output version
"""
green(__version__)
2014-10-17 00:54:45 +03:00
2014-10-17 06:18:57 +03:00
2014-11-11 22:48:11 +02:00
@cli.command(add_help_option=False)
def deploy():
""" Deploy to PyPI and docker hub
"""
call("python setup.py sdist upload", shell=True)
call("sudo docker build --no-cache -t realms/realms-wiki %s/docker" % app.config['APP_PATH'], shell=True)
id_ = json.loads(Popen("sudo docker inspect realms/realms-wiki".split(), stdout=subprocess.PIPE).communicate()[0])[0]['Id']
call("sudo docker tag %s realms/realms-wiki:%s" % (id_, __version__), shell=True)
call("sudo docker push realms/realms-wiki", shell=True)
2014-10-17 00:54:45 +03:00
if __name__ == '__main__':
cli()