From 07852bdd981977a5424eb2e8b7502293b0d3c2a1 Mon Sep 17 00:00:00 2001 From: Matthew Scragg Date: Thu, 16 Oct 2014 16:54:45 -0500 Subject: [PATCH 1/2] cli and setuptools wip --- .gitignore | 6 +- .travis.yml | 2 +- MANIFEST.in | 6 + README.md => README | 0 VERSION | 2 +- Vagrantfile | 2 +- install.sh | 34 +- manage.py | 195 ----------- realms-wiki | 6 + realms/__init__.py | 9 +- realms/cli.py | 350 +++++++++++++++++++ realms/config/__init__.py | 59 ++-- realms/lib/util.py | 47 +++ realms/modules/wiki/tests.py | 2 + dev-requirements.txt => requirements-dev.txt | 0 requirements.txt | 1 + setup.py | 17 +- 17 files changed, 496 insertions(+), 242 deletions(-) create mode 100644 MANIFEST.in rename README.md => README (100%) delete mode 100755 manage.py create mode 100755 realms-wiki create mode 100644 realms/cli.py rename dev-requirements.txt => requirements-dev.txt (100%) diff --git a/.gitignore b/.gitignore index 5425854..89b504e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,14 @@ .vagrant .venv +.venv-pypy .idea .webassets-cache *.pyc *.egg-info *.pid -dist -build +pidfile +/dist +/build config.py config.sls config.json diff --git a/.travis.yml b/.travis.yml index ef9e077..d481d69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ python: before_install: - sudo apt-get install -y libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libyaml-dev libssl-dev -install: "pip install -r dev-requirements.txt" +install: "pip install -r requirements-dev.txt" script: nosetests diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..41985cd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include requirements.txt VERSION LICENSE +recursive-include realms/static/css * +recursive-include realms/static/fonts * +recursive-include realms/static/js * +recursive-include realms/static/vendor * +recursive-include realms/templates * \ No newline at end of file diff --git a/README.md b/README similarity index 100% rename from README.md rename to README diff --git a/VERSION b/VERSION index a2268e2..a70b636 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.1 \ No newline at end of file +0.3.13 \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile index 9ec5efa..f4b36da 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,7 +6,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.provider :virtualbox do |vb| vb.name = "realms-wiki" vb.memory = 2048 - vb.cpus = 2 + vb.cpus = 4 end config.vm.provision "shell", path: "install.sh" diff --git a/install.sh b/install.sh index 5b438bd..31214b1 100755 --- a/install.sh +++ b/install.sh @@ -50,23 +50,51 @@ sudo -iu ${APP_USER} bower --allow-root --config.cwd=${APP_DIR} --config.directo sudo -iu ${APP_USER} virtualenv ${APP_DIR}/.venv -sudo -iu ${APP_USER} ${APP_DIR}/.venv/bin/pip install ${APP_DIR} +cd ${APP_DIR} && sudo -iu ${APP_USER} ${APP_DIR}/.venv/bin/pip install -r ${APP_DIR}/requirements-dev.txt echo "Installing start scripts" cat << EOF > /usr/local/bin/realms-wiki #!/bin/bash -${APP_DIR}/.venv/bin/python ${APP_DIR}/manage.py "\$@" +${APP_DIR}/realms-wiki "\$@" EOF sudo chmod +x /usr/local/bin/realms-wiki cat << EOF > /etc/init/realms-wiki.conf +limit nofile 65335 65335 + +respawn + description "Realms Wiki" author "scragg@gmail.com" + +chdir ${APP_DIR} + +env PATH=${APP_DIR}/.venv/bin:/usr/local/bin:/usr/bin:/bin:$PATH +env LC_ALL=en_US.UTF-8 +env GEVENT_RESOLVER=ares + +export PATH +export LC_ALL +export GEVENT_RESOLVER + setuid ${APP_USER} setgid ${APP_USER} + start on runlevel [2345] stop on runlevel [!2345] + respawn -exec /usr/local/bin/realms-wiki run + +exec gunicorn \ + --name realms-wiki \ + --access-logfile - \ + --error-logfile - \ + --worker-class gevent \ + --workers 2 \ + --bind 0.0.0.0:5000 \ + --user ${APP_USER} \ + --group ${APP_USER} \ + --chdir ${APP_DIR} \ + wsgi:app EOF diff --git a/manage.py b/manage.py deleted file mode 100755 index 67b8736..0000000 --- a/manage.py +++ /dev/null @@ -1,195 +0,0 @@ -from gevent import wsgi -from realms import config, app, cli, db -from realms.lib.util import random_string -from subprocess import call -import click -import json - - -@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, - prompt='Where do you want to store wiki data?', - 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('--db-uri', - default=config.DB_URI, - prompt='Database URI, Examples: http://goo.gl/RyW0cl') -@click.pass_context -def setup(ctx, **kw): - """ Start setup wizard - """ - conf = {} - - for k, v in kw.items(): - conf[k.upper()] = v - - config.update(conf) - - if conf['CACHE_TYPE'] == 'redis': - ctx.invoke(setup_redis) - elif conf['CACHE_TYPE'] == 'memcached': - ctx.invoke(setup_memcached) - - click.secho('Config saved to %s/config.json' % config.APP_PATH, fg='green') - click.secho('Type "realms-wiki run" to start server', fg='yellow') - - -@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') -@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') -def setup_redis(**kw): - conf = {} - - for k, v in kw.items(): - conf[k.upper()] = v - - config.update(conf) - install_redis() - - -def get_prefix(): - import sys - return sys.prefix - - -def get_pip(): - """ Get virtualenv path for pip - """ - return get_prefix() + '/bin/pip' - - -@cli.command() -@click.argument('cmd', nargs=-1) -def pip(cmd): - """ Execute pip commands for this virtualenv - """ - call(get_pip() + ' ' + ' '.join(cmd), shell=True) - - -def install_redis(): - call([get_pip(), 'install', 'redis']) - - -def install_mysql(): - call([get_pip(), 'install', 'MySQL-Python']) - - -def install_postgres(): - call([get_pip(), 'install', 'psycopg2']) - - -def install_memcached(): - call([get_pip(), 'install', 'python-memcached']) - - -@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.argument('config_json') -def configure(config_json): - """ Set config.json, expects JSON encoded string - """ - try: - config.update(json.loads(config_json)) - except ValueError, e: - click.secho('Config value should be valid JSON', fg='red') - - -@cli.command() -@click.option('--port', default=5000) -def dev(port): - """ Run development server - """ - click.secho("Starting development server", fg='green') - app.run(host="0.0.0.0", - port=port, - debug=True) - - -@cli.command() -def run(): - """ Run production server - """ - click.secho("Server started. Env: %s Port: %s" % (config.ENV, config.PORT), fg='green') - wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever() - - -@cli.command() -def create_db(): - """ Creates DB tables - """ - click.echo("Creating all tables") - db.create_all() - - -@cli.command() -@click.confirmation_option(help='Are you sure you want to drop the db?') -def drop_db(): - """ Drops DB tables - """ - click.echo("Dropping all tables") - db.drop_all() - - -@cli.command() -def test(): - """ Run tests - """ - call([get_prefix() + "/bin/nosetests", config.APP_PATH]) - - -@cli.command() -def version(): - """ Output version - """ - with open('VERSION') as f: - return f.read().strip() - -if __name__ == '__main__': - cli() \ No newline at end of file diff --git a/realms-wiki b/realms-wiki new file mode 100755 index 0000000..6344837 --- /dev/null +++ b/realms-wiki @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from realms.cli import cli + +if __name__ == '__main__': + cli() \ No newline at end of file diff --git a/realms/__init__.py b/realms/__init__.py index c16d4e4..b6f81e4 100644 --- a/realms/__init__.py +++ b/realms/__init__.py @@ -1,10 +1,4 @@ import sys -if 'threading' in sys.modules: - del sys.modules['threading'] - -# Monkey patch stdlib. -import gevent.monkey -gevent.monkey.patch_all(aggressive=False, subprocess=True) # Set default encoding to UTF-8 reload(sys) @@ -12,7 +6,6 @@ reload(sys) sys.setdefaultencoding('utf-8') import time -import sys import json import httplib import traceback @@ -74,7 +67,7 @@ class Application(Flask): if hasattr(sources, 'commands'): cli.add_command(sources.commands.cli, name=module_name) - print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time)) + # print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time)) def make_response(self, rv): if rv is None: diff --git a/realms/cli.py b/realms/cli.py new file mode 100644 index 0000000..87368b6 --- /dev/null +++ b/realms/cli.py @@ -0,0 +1,350 @@ +from realms import config, app, cli, db +from realms.lib.util import random_string +from subprocess import call, Popen +from multiprocessing import cpu_count +import click +import json +import sys +import os + + +def get_user(): + for name in ('SUDO_USER', 'LOGNAME', 'USER', 'LNAME', 'USERNAME'): + user = os.environ.get(name) + if user: + return user + + +def in_virtualenv(): + return hasattr(sys, 'real_prefix') + + +def is_su(): + return os.geteuid() == 0 + + +def get_pid(): + try: + with file(config.PIDFILE) as f: + pid = f.read().strip() + return pid if pid and int(pid) > 0 and not call(['kill', '-s', '0', pid]) else False + except IOError: + return False + + +def module_exists(module_name): + try: + __import__(module_name) + except ImportError: + return False + else: + return True + + +@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, + prompt='Where do you want to store wiki data?', + 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('--db-uri', + default=config.DB_URI, + prompt='Database URI? Examples: http://goo.gl/RyW0cl') +@click.pass_context +def setup(ctx, **kw): + """ Start setup wizard + """ + + if not in_virtualenv() and not is_su(): + # This does not account for people the have user level python installs + # that aren't virtual environments! Should be rare I think + click.secho("Setup requires root privileges, use sudo or run as root") + return + + conf = {} + + for k, v in kw.items(): + conf[k.upper()] = v + + conf_path = config.update(conf) + + if conf['CACHE_TYPE'] == 'redis': + ctx.invoke(setup_redis) + elif conf['CACHE_TYPE'] == 'memcached': + ctx.invoke(setup_memcached) + + click.secho('Config saved to %s' % conf_path, fg='green') + click.secho('Type "realms-wiki start" to start server', fg='yellow') + click.secho('Type "realms-wiki dev" to start server in development mode', fg='yellow') + + +@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') +@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') +def setup_redis(**kw): + conf = {} + + for k, v in kw.items(): + conf[k.upper()] = v + + config.update(conf) + install_redis() + + +def get_prefix(): + return sys.prefix + + +def get_pip(): + """ Get virtualenv path for pip + """ + if not in_virtualenv() and not is_su(): + click.secho("This command requires root, use sudo or run as root") + return + + if in_virtualenv(): + return get_prefix() + '/bin/pip' + else: + return 'pip' + + +@cli.command() +@click.argument('cmd', nargs=-1) +def pip(cmd): + """ Execute pip commands, useful for virtualenvs + """ + call(get_pip() + ' ' + ' '.join(cmd), shell=True) + + +def install_redis(): + call([get_pip(), 'install', 'redis']) + + +def install_mysql(): + call([get_pip(), 'install', 'MySQL-Python']) + + +def install_postgres(): + call([get_pip(), 'install', 'psycopg2']) + + +def install_memcached(): + call([get_pip(), 'install', 'python-memcached']) + + +@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 + import realms + + print os.path.dirname(realms.__file__) + + if not is_su(): + click.secho("Please run this command as root or use sudo", fg='red') + return + + 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' + + with open('/etc/init/realms-wiki.conf', 'w') as f: + f.write(upstart_script(**kwargs)) + + click.secho('Wrote file to %s' % conf_file, fg='yellow') + click.echo("Type 'sudo start realms-wiki' to start") + click.echo("Type 'sudo stop realms-wiki' to stop") + click.echo("Type 'sudo restart realms-wiki' to restart") + + +@cli.command() +@click.argument('json_string') +def configure(json_string): + """ Set config.json, expects JSON encoded string + """ + try: + config.update(json.loads(json_string)) + except ValueError, e: + click.secho('Config value should be valid JSON', fg='red') + + +@cli.command() +@click.option('--port', default=5000) +def dev(port): + """ Run development server + """ + click.secho("Starting development server", fg='green') + app.run(host="0.0.0.0", + port=port, + debug=True) + + +def start_server(): + if get_pid(): + click.echo("Server is already running") + return + + flags = '--daemon --pid %s' % config.PIDFILE + + click.secho("Server started. Port: %s" % config.PORT, fg='green') + + Popen('gunicorn realms:app -b 0.0.0.0:%s -k gevent %s' % + (config.PORT, flags), shell=True, executable='/bin/bash') + + +def stop_server(): + pid = get_pid() + if not pid: + click.echo("Server is not running") + else: + click.echo("Shutting down server") + call(['kill', pid]) + + +@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 pid: + click.echo("Server is not running") + else: + click.echo("Server is running PID: %s" % pid) + + +@cli.command() +def create_db(): + """ Creates DB tables + """ + click.echo("Creating all tables") + db.create_all() + + +@cli.command() +@click.confirmation_option(help='Are you sure you want to drop the db?') +def drop_db(): + """ Drops DB tables + """ + click.echo("Dropping all tables") + db.drop_all() + + +@cli.command() +def test(): + """ Run tests + """ + for mod in [('flask.ext.testing', 'Flask-Testing'), ('nose', 'nose')]: + if not module_exists(mod[0]): + call([get_pip(), 'install', mod[1]]) + + nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests" + + call([nosetests, config.APP_PATH]) + + +@cli.command() +def version(): + """ Output version + """ + with open('VERSION') as f: + return f.read().strip() + +if __name__ == '__main__': + cli() \ No newline at end of file diff --git a/realms/config/__init__.py b/realms/config/__init__.py index f3c0416..d0810c3 100644 --- a/realms/config/__init__.py +++ b/realms/config/__init__.py @@ -6,28 +6,48 @@ from urlparse import urlparse def update(data): conf = read() conf.update(data) - save(data) + return save(data) def read(): conf = dict() - try: - with open(os.path.join(APP_PATH, 'config.json')) as f: - conf = json.load(f) - except IOError: - pass + for k, v in os.environ.items(): + if k.startswith('REALMS_'): + conf[k[7:]] = v + + for loc in os.curdir, os.path.expanduser("~"), "/etc/realms-wiki", os.environ.get("REALMS_WIKI_CONF"): + try: + if not loc: + continue + with open(os.path.join(loc, "realms-wiki.json")) as f: + conf.update(json.load(f)) + break + except IOError: + pass + + for k in ['APP_PATH', 'USER_HOME']: + if k in conf: + del conf[k] return conf def save(conf): - with open(os.path.join(APP_PATH, 'config.json'), 'w') as f: - f.write(json.dumps(conf, sort_keys=True, indent=4, separators=(',', ': ')).strip() + '\n') + for loc in "/etc/realms-wiki", os.path.expanduser("~"), os.curdir: + try: + with open(os.path.join(loc, 'realms-wiki.json'), 'w') as f: + f.write(json.dumps(conf, sort_keys=True, indent=4, separators=(',', ': ')).strip() + '\n') + return os.path.join(loc, 'realms-wiki.json') + except IOError: + pass APP_PATH = os.path.abspath(os.path.dirname(__file__) + "/../..") USER_HOME = os.path.abspath(os.path.expanduser("~")) +# Best to change to /var/run +PIDFILE = "/tmp/realms-wiki.pid" + ENV = 'DEV' DEBUG = True @@ -39,7 +59,7 @@ BASE_URL = 'http://localhost' SITE_TITLE = "Realms" # https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format -DB_URI = 'sqlite:///%s/wiki.db' % USER_HOME +DB_URI = 'sqlite:////tmp/wiki.db' # DB_URI = 'mysql://scott:tiger@localhost/mydatabase' # DB_URI = 'postgresql://scott:tiger@localhost/mydatabase' # DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname' @@ -67,7 +87,7 @@ RECAPTCHA_OPTIONS = {} SECRET_KEY = 'CHANGE_ME' # Path on file system where wiki data will reside -WIKI_PATH = os.path.join(APP_PATH, 'wiki') +WIKI_PATH = '/tmp/wiki' # Name of page that will act as home WIKI_HOME = 'home' @@ -78,7 +98,7 @@ REGISTRATION_ENABLED = True # Used by Flask-Login LOGIN_DISABLED = ALLOW_ANON -# None, firepad, or togetherjs +# None, firepad, and/or togetherjs COLLABORATION = 'togetherjs' # Required for firepad @@ -91,22 +111,7 @@ LOCKED = WIKI_LOCKED_PAGES ROOT_ENDPOINT = 'wiki.page' -__env = {} -for k, v in os.environ.items(): - if k.startswith('REALMS_'): - __env[k[7:]] = v - -globals().update(__env) - -try: - with open(os.path.join(APP_PATH, 'config.json')) as f: - __settings = json.load(f) - for k in ['APP_PATH', 'USER_HOME']: - if k in __settings: - del __settings[k] - globals().update(__settings) -except IOError: - pass +globals().update(read()) if BASE_URL.endswith('/'): BASE_URL = BASE_URL[-1] diff --git a/realms/lib/util.py b/realms/lib/util.py index 2dac2c1..47e8b14 100644 --- a/realms/lib/util.py +++ b/realms/lib/util.py @@ -4,6 +4,7 @@ import hashlib import json import string import random +from jinja2 import Template class AttrDict(dict): @@ -97,3 +98,49 @@ def to_canonical(s): def gravatar_url(email): return "//www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest() + +def upstart_script(user='root', app_dir=None, port=5000, workers=2, path=None): + script = """ +limit nofile 65335 65335 + +respawn + +description "Realms Wiki" +author "scragg@gmail.com" + +chdir {{ app_dir }} + +{% if path %} +env PATH={{ path }}:/usr/local/bin:/usr/bin:/bin:$PATH +export PATH +{% endif %} + +env LC_ALL=en_US.UTF-8 +env GEVENT_RESOLVER=ares + +export LC_ALL +export GEVENT_RESOLVER + +setuid {{ user }} +setgid {{ user }} + +start on runlevel [2345] +stop on runlevel [!2345] + +respawn + +exec gunicorn \ + --name realms-wiki \ + --access-logfile - \ + --error-logfile - \ + --worker-class gevent \ + --workers {{ workers }} \ + --bind 0.0.0.0:{{ port }} \ + --user {{ user }} \ + --group {{ user }} \ + --chdir {{ app_dir }} \ + realms:app + +""" + template = Template(script) + return template.render(user=user, app_dir=app_dir, port=port, workers=workers, path=path) diff --git a/realms/modules/wiki/tests.py b/realms/modules/wiki/tests.py index 2e5393a..4a95d16 100644 --- a/realms/modules/wiki/tests.py +++ b/realms/modules/wiki/tests.py @@ -15,9 +15,11 @@ class WikiTest(TestCase): self.assert_200(self.client.get(url_for("wiki.create"))) + """ Create a test page first! for route in ['page', 'edit', 'history', 'index']: rv = self.client.get(url_for("wiki.%s" % route, name='test')) self.assert_200(rv, "wiki.%s: %s" % (route, rv.status_code)) + """ def test_write_page(self): pass diff --git a/dev-requirements.txt b/requirements-dev.txt similarity index 100% rename from dev-requirements.txt rename to requirements-dev.txt diff --git a/requirements.txt b/requirements.txt index d74a097..473dcb3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ click==3.3 gevent==1.0.1 ghdiff==0.4 gittle==0.4.0 +gunicorn==19.1.1 itsdangerous==0.24 lxml==3.4.0 markdown2==2.3.0 diff --git a/setup.py b/setup.py index ac9c306..30b39f6 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,12 @@ from setuptools import setup, find_packages +import os + +if os.environ.get('USER', '') == 'vagrant': + del os.link DESCRIPTION = "Simple git based wiki" -with open('README.md') as f: +with open('README') as f: LONG_DESCRIPTION = f.read() with open('requirements.txt') as f: @@ -13,15 +17,20 @@ with open('VERSION') as f: CLASSIFIERS = [ 'Intended Audience :: Developers', - 'License :: OSI Approved :: GPLv2 License', - 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules'] + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content'] setup(name='realms-wiki', version=VERSION, packages=find_packages(), install_requires=required, + #scripts=['realms-wiki'], + entry_points={ + 'console_scripts': [ + 'realms-wiki = realms.cli:cli' + ]}, author='Matthew Scragg', author_email='scragg@gmail.com', maintainer='Matthew Scragg', From 5985bff7d6ff377dbf9f642c09ecd958c573fc81 Mon Sep 17 00:00:00 2001 From: Matthew Scragg Date: Thu, 16 Oct 2014 22:18:57 -0500 Subject: [PATCH 2/2] cli and setuptools wip --- .gitignore | 1 + VERSION | 2 +- realms-wiki | 2 +- realms/cli.py | 78 ++++++++++++++++++++++++++++----------------------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 89b504e..947368b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ pidfile config.py config.sls config.json +realms-wiki.json realms/static/vendor realms/static/assets/* /wiki.db diff --git a/VERSION b/VERSION index a70b636..0010ffe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.13 \ No newline at end of file +0.3.14 \ No newline at end of file diff --git a/realms-wiki b/realms-wiki index 6344837..7f797a5 100755 --- a/realms-wiki +++ b/realms-wiki @@ -3,4 +3,4 @@ from realms.cli import cli if __name__ == '__main__': - cli() \ No newline at end of file + cli() diff --git a/realms/cli.py b/realms/cli.py index 87368b6..8e6a4cb 100644 --- a/realms/cli.py +++ b/realms/cli.py @@ -8,6 +8,15 @@ import sys import os +def check_su(f): + if not in_virtualenv() and not is_su(): + # This does not account for people the have user level python installs + # that aren't virtual environments! Should be rare I think + red("This command requires root privileges, use sudo or run as root.") + sys.exit() + return f + + def get_user(): for name in ('SUDO_USER', 'LOGNAME', 'USER', 'LNAME', 'USERNAME'): user = os.environ.get(name) @@ -41,7 +50,20 @@ def module_exists(module_name): return True +def green(s): + click.secho(s, fg='green') + + +def yellow(s): + click.secho(s, fg='yellow') + + +def red(s): + click.secho(s, fg='red') + + @cli.command() +@check_su @click.option('--site-title', default=config.SITE_TITLE, prompt='Enter site title.') @@ -56,7 +78,7 @@ def module_exists(module_name): prompt='Enter secret key.') @click.option('--wiki-path', default=config.WIKI_PATH, - prompt='Where do you want to store wiki data?', + prompt='Enter wiki data directory.', help='Wiki Directory (git repo)') @click.option('--allow-anon', default=config.ALLOW_ANON, @@ -78,12 +100,6 @@ def setup(ctx, **kw): """ Start setup wizard """ - if not in_virtualenv() and not is_su(): - # This does not account for people the have user level python installs - # that aren't virtual environments! Should be rare I think - click.secho("Setup requires root privileges, use sudo or run as root") - return - conf = {} for k, v in kw.items(): @@ -96,9 +112,9 @@ def setup(ctx, **kw): elif conf['CACHE_TYPE'] == 'memcached': ctx.invoke(setup_memcached) - click.secho('Config saved to %s' % conf_path, fg='green') - click.secho('Type "realms-wiki start" to start server', fg='yellow') - click.secho('Type "realms-wiki dev" to start server in development mode', fg='yellow') + green('Config saved to %s' % conf_path) + yellow('Type "realms-wiki start" to start server') + yellow('Type "realms-wiki dev" to start server in development mode') @click.command() @@ -131,10 +147,6 @@ def get_prefix(): def get_pip(): """ Get virtualenv path for pip """ - if not in_virtualenv() and not is_su(): - click.secho("This command requires root, use sudo or run as root") - return - if in_virtualenv(): return get_prefix() + '/bin/pip' else: @@ -142,6 +154,7 @@ def get_pip(): @cli.command() +@check_su @click.argument('cmd', nargs=-1) def pip(cmd): """ Execute pip commands, useful for virtualenvs @@ -180,6 +193,7 @@ def setup_memcached(**kw): @cli.command() +@check_su @click.option('--user', default=get_user(), type=click.STRING, @@ -196,13 +210,6 @@ def setup_upstart(**kwargs): """ Start upstart conf creation wizard """ from realms.lib.util import upstart_script - import realms - - print os.path.dirname(realms.__file__) - - if not is_su(): - click.secho("Please run this command as root or use sudo", fg='red') - return if in_virtualenv(): app_dir = get_prefix() @@ -219,10 +226,10 @@ def setup_upstart(**kwargs): with open('/etc/init/realms-wiki.conf', 'w') as f: f.write(upstart_script(**kwargs)) - click.secho('Wrote file to %s' % conf_file, fg='yellow') - click.echo("Type 'sudo start realms-wiki' to start") - click.echo("Type 'sudo stop realms-wiki' to stop") - click.echo("Type 'sudo restart realms-wiki' to restart") + green('Wrote file to %s' % conf_file) + green("Type 'sudo start realms-wiki' to start") + green("Type 'sudo stop realms-wiki' to stop") + green("Type 'sudo restart realms-wiki' to restart") @cli.command() @@ -233,7 +240,7 @@ def configure(json_string): try: config.update(json.loads(json_string)) except ValueError, e: - click.secho('Config value should be valid JSON', fg='red') + red('Config value should be valid JSON') @cli.command() @@ -241,7 +248,7 @@ def configure(json_string): def dev(port): """ Run development server """ - click.secho("Starting development server", fg='green') + green("Starting development server") app.run(host="0.0.0.0", port=port, debug=True) @@ -249,12 +256,12 @@ def dev(port): def start_server(): if get_pid(): - click.echo("Server is already running") + yellow("Server is already running") return flags = '--daemon --pid %s' % config.PIDFILE - click.secho("Server started. Port: %s" % config.PORT, fg='green') + green("Server started. Port: %s" % config.PORT) Popen('gunicorn realms:app -b 0.0.0.0:%s -k gevent %s' % (config.PORT, flags), shell=True, executable='/bin/bash') @@ -263,9 +270,9 @@ def start_server(): def stop_server(): pid = get_pid() if not pid: - click.echo("Server is not running") + yellow("Server is not running") else: - click.echo("Shutting down server") + yellow("Shutting down server") call(['kill', pid]) @@ -304,16 +311,16 @@ def status(): """ pid = get_pid() if not pid: - click.echo("Server is not running") + yellow("Server is not running") else: - click.echo("Server is running PID: %s" % pid) + green("Server is running PID: %s" % pid) @cli.command() def create_db(): """ Creates DB tables """ - click.echo("Creating all tables") + green("Creating all tables") db.create_all() @@ -322,7 +329,7 @@ def create_db(): def drop_db(): """ Drops DB tables """ - click.echo("Dropping all tables") + yellow("Dropping all tables") db.drop_all() @@ -346,5 +353,6 @@ def version(): with open('VERSION') as f: return f.read().strip() + if __name__ == '__main__': cli() \ No newline at end of file