Esempio n. 1
0
class Command(LuxCommand):
    help = 'Create a superuser.'
    option_list = (Setting('username', ('--username', ), desc='Username'), )

    def run(self, options, interactive=False):
        username = options.username
        if not username:
            interactive = True
        request = self.app.wsgi_request()
        auth_backend = self.app.auth_backend
        if interactive:  # pragma    nocover
            try:
                # Get a username
                username = input('username: '******'username "%s" is not available' % username)
        token = auth_backend.create_token(request, user)
        if token:
            self.write("token created successfully.\n")
            self.write(token.id.hex)
        else:
            raise CommandError("Could not create token")
        return token.id.hex
Esempio n. 2
0
class Command(LuxCommand):
    help = 'Create a superuser.'
    option_list = (Setting('username', ('--username', ), desc='Username'), )

    def run(self, options, interactive=False):
        username = options.username
        if not username:
            interactive = True
        request = self.app.wsgi_request()
        auth_backend = self.app.auth_backend
        auth_backend.request(request.environ)

        if interactive:  # pragma    nocover
            def_username = get_def_username(request, auth_backend)
            input_msg = 'Username'
            if def_username:
                input_msg += ' (Leave blank to use %s)' % def_username
            while not username:
                username = input(input_msg + ': ')
                if def_username and username == '':
                    username = def_username

        user = auth_backend.get_user(request, username=username)
        if user is None:
            raise CommandError('user %s not available' % username)
        token = auth_backend.create_token(request, user)
        self.write('Token: %s' % as_hex(token.id))

        return token
Esempio n. 3
0
class Command(LuxCommand):
    help = "Generate a secret key."
    option_list = (
        Setting('length', ('--length', ),
                default=64,
                type=int,
                desc=('Secret key length')),
        Setting('hex', ('--hex', ),
                default=False,
                action='store_true',
                desc=('Hexadecimal string')),
    )

    def run(self, options, **params):
        key = generate_secret(options.length, hexadecimal=options.hex)
        self.write('Secret key:')
        self.write(key)
        self.write('-----------------------------------------------------')
        return key
Esempio n. 4
0
class Command(LuxCommand):
    option_list = (Setting(
        'dryrun', ('--dryrun', ),
        action='store_true',
        default=False,
        desc=("It does not remove any data, instead it displays "
              "the number of models which could be removed")),
                   Setting(
                       'models',
                       nargs='+',
                       desc='model1 model2 ... Use * to include all models'))
    help = "Flush models in the data servers"

    def run(self, options, interactive=True, yn='yes'):
        if options.models[0] == '*':
            models = list(self.app.models)
        else:
            models = []
            for model in options.models:
                if model in self.app.models:
                    models.append(model)
        if not models:
            return self.write('Nothing done. No models')
        #
        self.write('\nAre you sure you want to remove these models?\n')
        for model in sorted(models):
            self.write('%s' % model)
        #
        if options.dryrun:
            self.write('\nNothing done. Dry run')
        else:
            self.write('')
            yn = input('yes/no : ') if interactive else yn
            if yn.lower() == 'yes':
                request = self.app.wsgi_request()
                for model in models:
                    model = self.app.models[model]
                    with model.session(request) as session:
                        N = model.query(request, session).delete()
                        self.write('{0} - removed {1}'.format(model, N))
            else:
                self.write('Nothing done')
Esempio n. 5
0
class Command(LuxCommand):
    help = "Clear Sessions"
    option_list = (Setting('app_name',
                           nargs='?',
                           desc=('Optional app name. If omitted the default '
                                 'application name is used (APP_NAME)')), )

    def run(self, options, **params):
        result = session_backend(self.app).clear(options.app_name)
        self.write('Clear %d sessions' % result)
        return result
Esempio n. 6
0
class Command(LuxCommand):
    help = "Clear Cache"
    option_list = (
        Setting('prefix',
                nargs='?',
                desc=('Optional cache prefix. If omitted the default '
                      'application prefix is used (APP_NAME)')),
    )

    def run(self, options, **params):
        cache = self.app.cache_server
        result = cache.clear(options.prefix)
        self.write('Clear %d keys' % result)
        return result
Esempio n. 7
0
class Command(LuxCommand):
    help = "Stop a running server"

    option_list = (
        Setting('timeout', ('--timeout',),
                default=10, type=int,
                desc=('Timeout for waiting SIGTERM stop')),
    )
    pulsar_config_include = ('log_level', 'log_handlers', 'debug',
                             'config', 'pid_file')

    def run(self, options, **params):
        app = self.app
        pid_file = options.pid_file
        if pid_file:
            if os.path.isfile(pid_file):
                pid = Pidfile(pid_file).read()
                if not pid:
                    raise CommandError('No pid in pid file %s' % pid_file)
            else:
                raise CommandError('Could not located pid file %s' % pid_file)
        else:
            raise CommandError('Pid file not available')

        try:
            self.kill(pid, signal.SIGTERM)
        except ProcessLookupError:
            raise CommandError('Process %d does not exist' % pid) from None

        start = time.time()
        while time.time() - start < options.timeout:
            if os.path.isfile(pid_file):
                time.sleep(0.2)
            else:
                app.write('Process %d terminated' % pid)
                return 0

        app.write_err('Could not terminate process after %d seconds' %
                      options.timeout)
        self.kill(pid, signal.SIGKILL)
        app.write_err('Processed %d killed' % pid)
        return 1

    def kill(self, pid, sig):   # pragma    nocover
        os.kill(pid, sig)
Esempio n. 8
0
class Command(LuxCommand):
    help = "Show parameters."
    option_list = (Setting('extensions',
                           nargs='*',
                           desc='Extensions to display parameters from.'), )

    def run(self, options, **params):
        display = options.extensions
        config = self.app.config
        extensions = self.app.extensions
        for ext in chain([self.app], extensions.values()):
            if display and ext.meta.name not in display:
                continue
            if ext.meta.config:
                self.write('\n%s' % ext.meta.name)
                self.write('#=====================================')
            for key, value in ext.sorted_config():
                self.write('%s: %s' % (key, config[key]))
Esempio n. 9
0
class Command(LuxCommand):
    help = 'Regenerate an application token'
    option_list = (
        Setting('app_id',
                nargs='*',
                desc='Application ID'),
    )

    def run(self, options, **params):
        auth_backend = self.app.auth_backend
        request = self.app.wsgi_request(urlargs={}, app_handler=True)
        request.cache.auth_backend = auth_backend
        model = self.app.models['applications']
        app_id = options.app_id or request.config['MASTER_APPLICATION_ID']
        if not app_id:
            raise CommandError('app_id not available')
        try:
            app_domain = model.get_instance(request, id=app_id)
        except Http404:
            raise CommandError('app_id %s not available' % app_id) from None
        token = model.regenerate_token(request, app_domain)
        self.write(token)
        return token
Esempio n. 10
0
class Command(LuxCommand):
    option_list = (
        Setting('base_url', ['--base-url'],
                nargs=1,
                desc='Static website base url'),
        Setting('static_path', ['--static-path'],
                desc='Path where to install files'),
    )

    help = "create a static site"

    async def run(self, options):
        try:
            from bs4 import BeautifulSoup as bs
        except ImportError:
            raise CommandError(
                'Requires BeautifulSoup: pip install beautifulsoup4') from None

        path = options.static_path
        if not path:
            path = os.getcwd()
        if not options.base_url:
            raise CommandError('specify base url with --base-url flag')
        base = options.base_url[0]
        self.app.config['CACHE_SERVER'] = 'static://'
        self.bs = bs
        self.http = HttpTestClient(self.app.callable, wsgi=self.app)
        self.files = {}
        urls = ['%s/sitemap.xml' % base]
        self.app.cms.set_priority = False
        await self.build_from_rurls(path, base, urls)
        self.app.cms.set_priority = True
        self.app.cache_server.clear()
        self.files = {}
        await self.build_from_rurls(path, base, urls)

    async def build_from_rurls(self, path, base, urls):
        await gather(*[self.build(path, base, url) for url in urls])

    async def build(self, path, base, url):
        if url in self.files:
            self.app.logger.warning('Url %s already processed', url)
            return
        self.files[url] = None
        response = await self.http.get(url)
        response.raise_for_status()
        urlp = urlparse(url)
        #
        # save file
        html = response.text()
        rel_path = urlp.path
        if not rel_path:
            rel_path = 'index'

        if url.endswith('.xml'):
            soup = self.bs(response.content, 'html.parser')
            urls = []
            for u in chain(soup.findAll('sitemap'), soup.findAll('url')):
                loc = u.find('loc')
                if not loc:
                    continue
                url = loc.string
                if url.startswith(base):
                    if not self.app.cms.set_priority or url.endswith('.xml'):
                        urls.append(url)
            if urls:
                await self.build_from_rurls(path, base, urls)
        else:
            rel_path = '%s.html' % rel_path

        bits = [r for r in rel_path.split('/') if r]
        filename = os.path.join(path, *bits)
        dirname = os.path.dirname(filename)
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
        with open(filename, 'w') as fp:
            fp.write(html)
        self.files[url] = filename
Esempio n. 11
0
class Command(LuxCommand):
    help = 'Alembic commands for migrating database.'

    commands = ['auto', 'branches', 'current', 'downgrade', 'heads', 'history',
                'init', 'merge', 'revision', 'show', 'stamp', 'upgrade']

    option_list = (
        Setting('command',
                nargs='*', default=None, desc='Alembic command'),
        Setting('branch',
                ('-b', '--branch'), default=None, nargs='?',
                desc='Branch label for auto, revision and merge command',
                meta='LABEL'),
        Setting('list',
                ('--commands',), default=None, action='store_true',
                desc='List available Alembic commands'),
        Setting('msg',
                ('-m', '--message'), nargs='?', default=None,
                desc='Message for auto, revision and merge command'),
    )

    def run(self, opt):
        '''
        Run obvious commands and validate more complex.
        '''
        from alembic import util
        try:
            if opt.list:
                available = 'Alembic commands:\n%s' % ', '.join(self.commands)
                self.write(available)
                return available
            if opt.command:
                cmd = opt.command[0]
                if cmd not in self.commands:
                    raise CommandError('Unrecognized command %s' %
                                       opt.command[0])
                if cmd in ('auto', 'revision', 'merge') and not opt.msg:
                    raise CommandError('Missing [-m] parameter for %s' % cmd)
                self.run_alembic_cmd(opt)
                return True
            raise CommandError('Pass [--commands] for available commands')
        except util.CommandError as exc:
            raise CommandError(str(exc))

    def get_lux_template_directory(self):
        return os.path.join(os.path.dirname(os.path.realpath(__file__)),
                            'template')

    def get_config(self):
        '''
        Programmatically create Alembic config. To determine databases,
        DATASTORE from project's config file is used. To customize Alembic
        use MIGRATIONS in you config file.

        Example:
        MIGRATIONS = {
            'alembic': {
                'script_location': '<path>',
                'databases': '<db_name1>,<db_name2>',
            },
            '<db_name1>': {
                'sqlalchemy.url': 'driver://*****:*****@localhost/dbname',
            },
            '<bd_name2>': {
                'sqlalchemy.url': 'driver://*****:*****@localhost/dbname',
            },
            'logging': {
                'path': '<path_to_logging_config>',
            }
        }

        For more information about possible options, please visit Alembic
        documentation:
        https://alembic.readthedocs.org/en/latest/index.html
        '''
        from alembic.config import Config
        # Because we are using custom template, we need to change default
        # implementation of get_template_directory() to make it pointing
        # to template stored in lux, we need to do this hack
        Config.get_template_directory = self.get_lux_template_directory

        # put migrations in main project dir
        migration_dir = os.path.join(self.app.meta.path, 'migrations')
        # set default settings in case where MIGRATIONS is not set
        alembic_cfg = Config()
        # where to place alembic env
        alembic_cfg.set_main_option('script_location', migration_dir)
        # get database(s) name(s) and location(s)
        odm = self.app.odm()
        databases = []
        # set section for each found database
        for name, engine in odm.keys_engines():
            if not name:
                name = 'default'
            databases.append(name)
            # url = str(engine.url).replace('+green', '')
            url = str(engine.url)
            alembic_cfg.set_section_option(name, 'sqlalchemy.url', url)
        # put databases in main options
        alembic_cfg.set_main_option("databases", ','.join(databases))
        # create empty logging section to avoid raising errors in env.py
        alembic_cfg.set_section_option('logging', 'path', '')
        # obtain the metadata required for `auto` command
        self.get_metadata(alembic_cfg)

        # get rest of settings from project config. This may overwrite
        # already existing options (especially if different migration dir
        # is provided)
        cfg = self.app.config.get('MIGRATIONS')
        if cfg:
            for section in cfg.keys():
                for key, value in cfg[section].items():
                    if section == 'alembic':
                        alembic_cfg.set_main_option(key, value)
                    else:
                        alembic_cfg.set_section_option(section, key, value)

        return alembic_cfg

    def run_alembic_cmd(self, opt):
        '''
        Logic for running different Alembic commands.
        '''
        from alembic import command as alembic_cmd

        config = self.get_config()
        # command consume any number of parameters but first is command name
        cmd = opt.command.pop(0)
        # init command needs to point to lux template, not alembic default
        if cmd == 'init':
            dirname = config.get_main_option('script_location')
            # line 63 will be executed in:
            # https://github.com/zzzeek/alembic/blob/master/alembic/command.py
            # since we do not use any *.ini file, we simply silence error
            # about referenced before assignment as it have no negative impact.
            try:
                alembic_cmd.init(config, dirname, template='lux')
            except UnboundLocalError:  # pragma nocover
                pass
        # merge required two revision name
        elif cmd == 'merge':
            if len(opt.command) != 2:
                raise CommandError('Command %s required revisions id.' % cmd)
            alembic_cmd.merge(config, opt.command, message=opt.msg,
                              branch_label=opt.branch)
        elif cmd == 'revision':
            alembic_cmd.revision(config, message=opt.msg,
                                 branch_label=opt.branch)
        # auto command is a shortcut for `revision --autogenerate`
        elif cmd == 'auto':
            alembic_cmd.revision(config, autogenerate=True, message=opt.msg,
                                 branch_label=opt.branch)
        # this commands required revision name, but do not take any message or
        # branch labels
        elif cmd in ('show', 'stamp', 'upgrade', 'downgrade'):
            if len(opt.command) != 1:
                raise CommandError('Command %s required revision id' % cmd)
            getattr(alembic_cmd, cmd)(config, *opt.command)
        else:
            # execute commands without any additional params
            getattr(alembic_cmd, cmd)(config)

    def get_metadata(self, config):
        '''
        MetaData object stored in odm extension contains aggregated data
        from all databases defined in project. This function splits the data
        to correspond with related database only.
        '''
        from sqlalchemy import MetaData
        odm = self.app.odm()
        metadata = {}

        for key, db_engine in odm.keys_engines():
            if not key:
                key = 'default'
            metadata[key] = meta = MetaData()
            for table, engine in odm.binds.items():
                if engine == db_engine:
                    table.tometadata(meta)

        config.metadata = metadata
Esempio n. 12
0
import pulsar
from pulsar.apps import wsgi
from pulsar.utils.log import clear_logger

from lux.core import LuxCommand, Setting

nominify = Setting('nominify', ['--nominify'],
                   action="store_true",
                   default=False,
                   desc="Don't use minified media files")


class Command(LuxCommand):
    help = "Starts a fully-functional Web server using pulsar"
    option_list = (nominify, )
    wsgiApp = wsgi.WSGIServer

    def __call__(self, argv, start=True, get_app=False):
        self.app.callable.command = self.name
        app = self.app
        server = self.pulsar_app(argv, self.wsgiApp)
        if server.cfg.nominify:
            app.params['MINIFIED_MEDIA'] = False

        if start and not server.logger:  # pragma    nocover
            if not pulsar.get_actor():
                clear_logger()
            app._started = server()
            app.on_start(server)
            arbiter = pulsar.arbiter()
            arbiter.start()
Esempio n. 13
0
class Command(LuxCommand):
    option_list = (Setting('luxname', nargs='?',
                           desc='Name of the project.'), )
    help = ('Creates a Lux project directory structure for the given '
            'project name in the current directory or optionally in the '
            'given directory.')

    def run(self, options):
        if not options.luxname:
            raise CommandError("A project name is required")
        name = options.luxname
        validate_name(name)
        target = path.join(os.getcwd(), '%s-project' % name)

        if path.exists(target):
            raise CommandError("%r conflicts with an existing path" % target)

        # Check that the name cannot be imported.
        try:
            import_module(name)
        except ImportError:
            pass
        else:
            raise CommandError("%r conflicts with the name of an "
                               "existing Python module and cannot be "
                               "used as a name.\n"
                               "Please try another name." % name)
        #
        # if some directory is given, make sure it's nicely expanded
        try:
            os.makedirs(target)
        except OSError as e:
            raise CommandError(str(e)) from e

        self.build(name, target)
        self.write('Project "%s" created' % name)
        return target

    def build(self, name, target):
        render = self.app.template_engine()
        base_name = 'project_name'
        context = {base_name: name, 'secret_key': generate_secret(50)}
        prefix_length = len(template_dir) + 1

        for root, dirs, files in os.walk(template_dir):
            path_rest = root[prefix_length:]
            relative_dir = path_rest.replace(base_name, name)

            if relative_dir:
                target_dir = path.join(target, relative_dir)
                if not path.exists(target_dir):
                    os.mkdir(target_dir)

            for dirname in dirs[:]:
                if skipfile(dirname):
                    dirs.remove(dirname)

            for filename in files:
                if (skipfile(filename) or filename.endswith(
                    ('.pyo', '.pyc', '.py.class'))):
                    continue

                old_path = path.join(root, filename)

                with open(old_path, 'r') as template_file:
                    content = template_file.read()

                if not filename.endswith('.html'):
                    content = render(content, context)

                new_path = path.join(target, relative_dir,
                                     filename.replace(base_name, name))

                with open(new_path, 'w') as new_file:
                    new_file.write(content)
Esempio n. 14
0
class Command(LuxCommand):
    help = 'Create a superuser.'
    option_list = (Setting('username', ('--username', ), desc='Username'),
                   Setting('password', ('--password', ), desc='Password'),
                   Setting('email', ('--email', ), desc='Email'))

    def run(self, options, interactive=False):
        username = options.username
        password = options.password
        email = options.email
        if not username or not password or not email:
            interactive = True
        request = self.app.wsgi_request()
        auth_backend = self.app.auth_backend
        auth_backend.request(request.environ)

        if interactive:  # pragma    nocover
            def_username = get_def_username(request, auth_backend)
            input_msg = 'Username'
            if def_username:
                input_msg += ' (Leave blank to use %s)' % def_username
            username = None
            email = None
            password = None
            try:
                # Get a username
                while not username:
                    username = input(input_msg + ': ')
                    if def_username and username == '':
                        username = def_username
                    if not RE_VALID_USERNAME.match(username):
                        self.write_err('Error: That username is invalid. Use '
                                       'only letters, digits and underscores.')
                        username = None
                    else:
                        user = auth_backend.get_user(request,
                                                     username=username)
                        if user is not None:
                            self.write_err(
                                "Error: That username is already taken.\n")
                            username = None

                while not email:
                    email = input('Email: ')
                    try:
                        email = normalise_email(email)
                    except Exception:
                        self.write_err('Error: That email is invalid.')
                        email = None
                    else:
                        user = auth_backend.get_user(request, email=email)
                        if user is not None:
                            self.write_err(
                                "Error: That email is already taken.")
                            email = None

                # Get a password
                while 1:
                    if not password:
                        password = getpass.getpass()
                        password2 = getpass.getpass('Password (again): ')
                        if password != password2:
                            self.write_err(
                                "Error: Your passwords didn't match.")
                            password = None
                            continue
                    if password.strip() == '':
                        self.write_err(
                            "Error: Blank passwords aren't allowed.")
                        password = None
                        continue
                    break
            except KeyboardInterrupt:
                self.write_err('\nOperation cancelled.')
                return
        user = auth_backend.create_superuser(request,
                                             username=username,
                                             email=normalise_email(email),
                                             password=password)
        if user:
            self.write("Superuser %s created successfully.\n" % user.username)
        else:
            self.write_err("ERROR: could not create superuser")

        return user