Changeset - 7c2704784297
[Not reviewed]
stable
0 5 0
Mads Kiilerich (mads) - 18 months ago 2024-09-22 19:08:29
mads@kiilerich.com
Grafted from: ec090899c2b7
wsgi: Refactor to introduce make_app_raw function with the essentials of make_app

Command line commands and WSGI applications have different environments and
runtime models. They deserve different handling.

Keep make_app doing as before so WSGI scripts keep working as before.

All "app" usage from command line is changed to use make_app_raw directly.
5 files changed with 10 insertions and 4 deletions:
0 comments (0 inline, 0 general)
kallithea/bin/kallithea_cli_base.py
Show inline comments
 
@@ -34,53 +34,53 @@ import kallithea.config.application
 
# can be used to invoke 'kallithea-cli' later.
 
kallithea_cli_path = sys.argv[0]
 

	
 

	
 
def read_config(ini_file_name, strip_section_prefix):
 
    """Read ini_file_name content, and for all sections like '[X:Y]' where X is
 
    strip_section_prefix, replace the section name with '[Y]'."""
 

	
 
    def repl(m):
 
        if m.group(1) == strip_section_prefix:
 
            return '[%s]' % m.group(2)
 
        return m.group(0)
 

	
 
    with open(ini_file_name) as f:
 
        return re.sub(r'^\[([^:]+):(.*)]', repl, f.read(), flags=re.MULTILINE)
 

	
 

	
 
# This placeholder is the main entry point for the kallithea-cli command
 
@click.group(context_settings=dict(help_option_names=['-h', '--help']))
 
def cli():
 
    """Various commands to manage a Kallithea instance."""
 

	
 
def register_command(needs_config_file=False, config_file_initialize_app=False, hidden=False):
 
    """Register a kallithea-cli subcommand.
 

	
 
    If one of the needs_config_file flags are true, a config file must be specified
 
    with -c and it is read and logging is configured. The configuration is
 
    available in the kallithea.CONFIG dict.
 

	
 
    If config_file_initialize_app is true, Kallithea, TurboGears global state
 
    (including tg.config), and database access will also be fully initialized.
 
    """
 
    cli_command = cli.command(hidden=hidden)
 
    if needs_config_file or config_file_initialize_app:
 
        def annotator(annotated):
 
            @click.option('--config_file', '-c', help="Path to .ini file with app configuration.",
 
                type=click.Path(dir_okay=False, exists=True, readable=True), required=True)
 
            @functools.wraps(annotated) # reuse meta data from the wrapped function so click can see other options
 
            def runtime_wrapper(config_file, *args, **kwargs):
 
                path_to_ini_file = os.path.realpath(config_file)
 
                config = paste.deploy.appconfig('config:' + path_to_ini_file)
 
                cp = configparser.ConfigParser(strict=False)
 
                cp.read_string(read_config(path_to_ini_file, strip_section_prefix=annotated.__name__))
 
                logging.config.fileConfig(cp,
 
                    {'__file__': path_to_ini_file, 'here': os.path.dirname(path_to_ini_file)})
 
                if needs_config_file:
 
                    annotated(*args, config=config, **kwargs)
 
                if config_file_initialize_app:
 
                    kallithea.config.application.make_app(config.global_conf, **config.local_conf)
 
                    kallithea.config.application.make_app_raw(config.global_conf, **config.local_conf)
 
                    annotated(*args, **kwargs)
 
            return cli_command(runtime_wrapper)
 
        return annotator
 
    return cli_command
kallithea/bin/kallithea_cli_celery.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import click
 
from celery.bin.celery import celery as celery_command
 

	
 
import kallithea
 
import kallithea.bin.kallithea_cli_base as cli_base
 
from kallithea.lib import celery_app
 
from kallithea.lib.utils2 import asbool
 

	
 

	
 
@cli_base.register_command(needs_config_file=True)
 
@click.argument('celery_args', nargs=-1)
 
def celery_run(celery_args, config):
 
    """Start Celery worker(s) for asynchronous tasks.
 

	
 
    This commands starts the Celery daemon which will spawn workers to handle
 
    certain asynchronous tasks for Kallithea.
 

	
 
    Any extra arguments you pass to this command will be passed through to
 
    Celery. Use '--' before such extra arguments to avoid options to be parsed
 
    by this CLI command.
 
    """
 

	
 
    if not asbool(config.get('use_celery')):
 
        raise Exception('Please set use_celery = true in .ini config '
 
                        'file before running this command')
 

	
 
    kallithea.CELERY_APP.config_from_object(celery_app.make_celery_config(config))
 

	
 
    kallithea.CELERY_APP.loader.on_worker_process_init = lambda: kallithea.config.application.make_app(config.global_conf, **config.local_conf)
 
    kallithea.CELERY_APP.loader.on_worker_process_init = lambda: kallithea.config.application.make_app_raw(config.global_conf, **config.local_conf)
 

	
 
    args = list(celery_args)
 
    # args[0] is generally ignored when prog_name is specified, but -h *needs* it to be 'worker' ... but will also suggest that users specify 'worker' explicitly
 
    if not args or args[0] != 'worker':
 
        args.insert(0, 'worker')
 

	
 
    # inline kallithea.CELERY_APP.start in order to allow specifying prog_name
 
    assert celery_command.params[0].name == 'app'
 
    celery_command.params[0].default = kallithea.CELERY_APP
 
    celery_command.main(args=args, prog_name='kallithea-cli celery-run -c CONFIG_FILE --')
kallithea/bin/vcs_hooks.py
Show inline comments
 
@@ -71,97 +71,97 @@ def update(ui, repo, hooktype=None, **kw
 

	
 
    Called as Mercurial hook changegroup.kallithea_update after push.
 
    """
 
    try:
 
        ui.pushbuffer(error=True, subproc=True)
 
        rev = brev = None
 
        mercurial.hg.updatetotally(ui, repo, rev, brev)
 
    finally:
 
        s = ui.popbuffer()  # usually just "x files updated, x files merged, x files removed, x files unresolved"
 
        log.info('%s update hook output: %s', safe_str(repo.root), safe_str(s).rstrip())
 

	
 

	
 
def pull_action(ui, repo, **kwargs):
 
    """Logs user pull action
 

	
 
    Called as Mercurial hook outgoing.kallithea_pull_action.
 
    """
 
    hooks.log_pull_action()
 

	
 

	
 
def push_action(ui, repo, node, node_last, **kwargs):
 
    """
 
    Register that changes have been added to the repo - log the action *and* invalidate caches.
 
    Note: This hook is not only logging, but also the side effect invalidating
 
    caches! The function should perhaps be renamed.
 

	
 
    Called as Mercurial hook changegroup.kallithea_push_action .
 

	
 
    The pushed changesets is given by the revset 'node:node_last'.
 
    """
 
    revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])]
 
    hooks.process_pushed_raw_ids(revs)
 

	
 

	
 
def _git_hook_environment(repo_path):
 
    """
 
    Create a light-weight environment for stand-alone scripts and return an UI and the
 
    db repository.
 

	
 
    Git hooks are executed as subprocess of Git while Kallithea is waiting, and
 
    they thus need enough info to be able to create an app environment and
 
    connect to the database.
 
    """
 
    extras = get_hook_environment()
 

	
 
    path_to_ini_file = extras['config']
 
    config = paste.deploy.appconfig('config:' + path_to_ini_file)
 
    #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
 
    kallithea.config.application.make_app(config.global_conf, **config.local_conf)
 
    kallithea.config.application.make_app_raw(config.global_conf, **config.local_conf)
 

	
 
    # fix if it's not a bare repo
 
    if repo_path.endswith(os.sep + '.git'):
 
        repo_path = repo_path[:-5]
 

	
 
    repo = db.Repository.get_by_full_path(repo_path)
 
    if not repo:
 
        raise OSError('Repository %s not found in database' % repo_path)
 

	
 
    return repo
 

	
 

	
 
def post_receive(repo_path, git_stdin_lines):
 
    """Called from Git post-receive hook.
 
    The returned value is used as hook exit code and must be 0.
 
    """
 
    try:
 
        repo = _git_hook_environment(repo_path)
 
    except HookEnvironmentError as e:
 
        sys.stderr.write("Skipping Kallithea Git post-receive hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e))
 
        return 0
 

	
 
    # the post push hook should never use the cached instance
 
    scm_repo = repo.scm_instance_no_cache()
 

	
 
    rev_data = []
 
    for l in git_stdin_lines:
 
        old_rev, new_rev, ref = l.strip().split(' ')
 
        _ref_data = ref.split('/')
 
        if _ref_data[1] in ['tags', 'heads']:
 
            rev_data.append({'old_rev': old_rev,
 
                             'new_rev': new_rev,
 
                             'ref': ref,
 
                             'type': _ref_data[1],
 
                             'name': '/'.join(_ref_data[2:])})
 

	
 
    git_revs = []
 
    for push_ref in rev_data:
 
        _type = push_ref['type']
 
        if _type == 'heads':
 
            if push_ref['old_rev'] == EmptyChangeset().raw_id:
 
                # update the symbolic ref if we push new repo
 
                if scm_repo.is_empty():
 
                    scm_repo._repo.refs.set_symbolic_ref(
 
                        b'HEAD',
 
                        b'refs/heads/%s' % safe_bytes(push_ref['name']))
 

	
 
                # build exclude list without the ref
kallithea/config/application.py
Show inline comments
 
@@ -3,66 +3,72 @@
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""WSGI middleware initialization for the Kallithea application."""
 

	
 
from kallithea.config.app_cfg import base_config
 
from kallithea.config.middleware.https_fixup import HttpsFixup
 
from kallithea.config.middleware.permanent_repo_url import PermanentRepoUrl
 
from kallithea.config.middleware.simplegit import SimpleGit
 
from kallithea.config.middleware.simplehg import SimpleHg
 
from kallithea.config.middleware.wrapper import RequestWrapper
 
from kallithea.lib.utils2 import asbool
 

	
 

	
 
__all__ = ['make_app']
 

	
 

	
 
def wrap_app(app):
 
    """Wrap the TG WSGI application in Kallithea middleware"""
 
    config = app.config
 

	
 
    # we want our low level middleware to get to the request ASAP. We don't
 
    # need any stack middleware in them - especially no StatusCodeRedirect buffering
 
    app = SimpleHg(app, config)
 
    app = SimpleGit(app, config)
 

	
 
    # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy
 
    if config.get('url_scheme_variable') or asbool(config.get('force_https')) or asbool(config.get('use_htsts')):
 
        app = HttpsFixup(app, config)
 

	
 
    app = PermanentRepoUrl(app, config)
 

	
 
    # Optional and undocumented wrapper - gives more verbose request/response logging, but has a slight overhead
 
    if asbool(config.get('use_wsgi_wrapper')):
 
        app = RequestWrapper(app, config)
 

	
 
    return app
 

	
 

	
 
def make_app(global_conf, **app_conf):
 
    """Return WSGI app with logging Mercurial stdout/stderr - to be used as
 
    Paste or mod_wsgi entry point"""
 
    return make_app_raw(global_conf, **app_conf)
 

	
 

	
 
def make_app_raw(global_conf, **app_conf):
 
    """
 
    Set up Kallithea with the settings found in the PasteDeploy configuration
 
    file used.
 

	
 
    :param global_conf: The global settings for Kallithea (those
 
        defined under the ``[DEFAULT]`` section).
 
    :return: The Kallithea application with all the relevant middleware
 
        loaded.
 

	
 
    This is the PasteDeploy factory for the Kallithea application.
 

	
 
    ``app_conf`` contains all the application-specific settings (those defined
 
    under ``[app:main]``.
 
    """
 
    assert app_conf.get('sqlalchemy.url')  # must be called with a Kallithea .ini file, which for example must have this config option
 
    assert global_conf.get('here') and global_conf.get('__file__')  # app config should be initialized the paste way ...
 

	
 
    return base_config.make_wsgi_app(global_conf, app_conf, wrap_app=wrap_app)
kallithea/tests/scripts/manual_test_concurrency.py
Show inline comments
 
@@ -2,97 +2,97 @@
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.tests.scripts.manual_test_concurrency
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Test suite for making push/pull operations
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Dec 30, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 

	
 
"""
 

	
 
import logging
 
import os
 
import shutil
 
import sys
 
import tempfile
 
from os.path import dirname
 
from subprocess import PIPE, Popen
 

	
 
from paste.deploy import appconfig
 
from sqlalchemy import engine_from_config
 

	
 
import kallithea.config.application
 
from kallithea.lib.utils2 import get_crypt_password
 
from kallithea.model import db, meta
 
from kallithea.model.base import init_model
 
from kallithea.model.repo import RepoModel
 
from kallithea.tests.base import HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS
 

	
 

	
 
rel_path = dirname(dirname(dirname(dirname(os.path.abspath(__file__)))))
 
conf = appconfig('config:development.ini', relative_to=rel_path)
 
kallithea.config.application.make_app(conf.global_conf, **conf.local_conf)
 
kallithea.config.application.make_app_raw(conf.global_conf, **conf.local_conf)
 

	
 
USER = TEST_USER_ADMIN_LOGIN
 
PASS = TEST_USER_ADMIN_PASS
 
HOST = 'server.local'
 
METHOD = 'pull'
 
DEBUG = True
 
log = logging.getLogger(__name__)
 

	
 

	
 
class Command(object):
 

	
 
    def __init__(self, cwd):
 
        self.cwd = cwd
 

	
 
    def execute(self, cmd, *args):
 
        """Runs command on the system with given ``args``.
 
        """
 

	
 
        command = cmd + ' ' + ' '.join(args)
 
        log.debug('Executing %s', command)
 
        if DEBUG:
 
            print(command)
 
        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
 
        stdout, stderr = p.communicate()
 
        if DEBUG:
 
            print(stdout, stderr)
 
        return stdout, stderr
 

	
 

	
 
def get_session():
 
    engine = engine_from_config(conf, 'sqlalchemy.')
 
    init_model(engine)
 
    sa = meta.Session
 
    return sa
 

	
 

	
 
def create_test_user(force=True):
 
    print('creating test user')
 
    sa = get_session()
 

	
 
    user = sa.query(db.User).filter(db.User.username == USER).scalar()
 

	
 
    if force and user is not None:
 
        print('removing current user')
 
        for repo in sa.query(db.Repository).filter(db.Repository.user == user).all():
 
            sa.delete(repo)
 
        sa.delete(user)
 
        sa.commit()
0 comments (0 inline, 0 general)