Changeset - 1ef52a70f3b7
[Not reviewed]
default
1 9 0
Marcin Kuzminski - 15 years ago 2010-07-14 02:28:32
marcin@python-works.com
Made config file free configuration based on database and capable of beeing manage via application settings + some code cleanups
10 files changed with 141 insertions and 98 deletions:
0 comments (0 inline, 0 general)
pylons_app/config/environment.py
Show inline comments
 
"""Pylons environment configuration"""
 
from mako.lookup import TemplateLookup
 
from pylons.configuration import PylonsConfig
 
from pylons.error import handle_mako_error
 
from pylons_app.config.routing import make_map
 
from pylons_app.lib.auth import set_available_permissions, set_base_path
 
from pylons_app.lib.utils import repo2db_mapper
 
from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
 
from pylons_app.model import init_model
 
from pylons_app.model.hg_model import _get_repos_cached_initial
 
from sqlalchemy import engine_from_config
 
import logging
 
import os
 
import pylons_app.lib.app_globals as app_globals
 
import pylons_app.lib.helpers
 

	
 
log = logging.getLogger(__name__)
 

	
 
def load_environment(global_conf, app_conf):
 
    """Configure the Pylons environment via the ``pylons.config``
 
@@ -52,19 +52,22 @@ def load_environment(global_conf, app_co
 
    
 
    #MULTIPLE DB configs
 
    # Setup the SQLAlchemy database engine
 
    if config['debug']:
 
        #use query time debugging.
 
        from pylons_app.lib.timerproxy import TimerProxy
 
        sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
 
                                                            proxy=TimerProxy())
 
    else:
 
        sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
 

	
 
    init_model(sa_engine_db1)
 
    config['pylons.app_globals'].baseui = make_ui('db')
 
    
 
    repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals']))
 
    set_available_permissions(config)
 
    set_base_path(config)
 
    set_hg_app_config(config)
 
    # CONFIGURATION OPTIONS HERE (note: all config options will override
 
    # any Pylons config options)
 
    
 
    return config
pylons_app/config/middleware.py
Show inline comments
 
@@ -32,25 +32,24 @@ def make_app(global_conf, full_stack=Tru
 
    """
 
    # Configure the Pylons environment
 
    config = load_environment(global_conf, app_conf)
 

	
 
    # The Pylons WSGI app
 
    app = PylonsApp(config=config)
 
    
 
    # Routing/Session/Cache Middleware
 
    app = RoutesMiddleware(app, config['routes.map'])
 
    app = SessionMiddleware(app, config)
 
    
 
    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
 
    #set the https based on HTTP_X_URL_SCHEME
 
    
 
    app = SimpleHg(app, config)
 
    
 
    if asbool(full_stack):
 
        # Handle Python exceptions
 
        app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
 

	
 
        # Display error documents for 401, 403, 404 status codes (and
 
        # 500 when debug is disabled)
 
        if asbool(config['debug']):
 
            app = StatusCodeRedirect(app)
 
        else:
pylons_app/config/repositories.config_tmpl
Show inline comments
 
deleted file
pylons_app/controllers/settings.py
Show inline comments
 
@@ -61,26 +61,24 @@ class SettingsController(BaseController)
 
        for p in c.repo_info.repo2perm:
 
            defaults.update({'perm_%s' % p.user.username: 
 
                             p.permission.permission_name})
 
            
 
        return htmlfill.render(
 
            render('settings/repo_settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )  
 

	
 
    def update(self, repo_name):
 
        print request.POST
 
        print 'x' * 110
 
        repo_model = RepoModel()
 
        _form = RepoSettingsForm(edit=True)()
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            repo_model.update(repo_name, form_result)
 
            invalidate_cache('cached_repo_list')
 
            h.flash(_('Repository %s updated succesfully' % repo_name),
 
                    category='success')
 
                           
 
        except formencode.Invalid as errors:
 
            c.repo_info = repo_model.get(repo_name)
 
            c.users_array = repo_model.get_users_js()
pylons_app/lib/app_globals.py
Show inline comments
 
"""The application's Globals object"""
 

	
 
from beaker.cache import CacheManager
 
from beaker.util import parse_cache_config_options
 
from pylons_app.lib.utils import make_ui
 
from vcs.utils.lazy import LazyProperty
 

	
 
class Globals(object):
 

	
 
    """Globals acts as a container for objects available throughout the
 
    life of the application
 

	
 
    """
 

	
 
    def __init__(self, config):
 
        """One instance of Globals is created during application
 
        initialization and is available during requests via the
 
        'app_globals' variable
 

	
 
        """
 
        self.cache = CacheManager(**parse_cache_config_options(config))
 
        self.baseui = make_ui(config['hg_app_repo_conf'])
 
        self.paths = self.baseui.configitems('paths')
 
        self.base_path = self.paths[0][1].replace('*', '')
 
        self.changeset_annotation_colors = {}
 
        self.available_permissions = None # propagated after init_model
 
        self.available_permissions = None   # propagated after init_model
 
        self.app_title = None               # propagated after init_model
 
        self.baseui = None                  # propagated after init_model        
 
        
 
    @LazyProperty
 
    def paths(self):
 
        if self.baseui:
 
            return self.baseui.configitems('paths')
 
    
 
    @LazyProperty
 
    def base_path(self):
 
        if self.baseui:
 
            return self.paths[0][1].replace('*', '')            
pylons_app/lib/db_manage.py
Show inline comments
 
@@ -24,25 +24,25 @@ database managment and creation for hg a
 
@author: marcink
 
"""
 

	
 
from os.path import dirname as dn, join as jn
 
import os
 
import sys
 
import uuid
 
ROOT = dn(dn(dn(os.path.realpath(__file__))))
 
sys.path.append(ROOT)
 

	
 
from pylons_app.lib.auth import get_crypt_password
 
from pylons_app.model import init_model
 
from pylons_app.model.db import User, Permission
 
from pylons_app.model.db import User, Permission, HgAppUi
 
from pylons_app.model.meta import Session, Base
 
from sqlalchemy.engine import create_engine
 
import logging
 

	
 
log = logging.getLogger('db manage')
 
log.setLevel(logging.DEBUG)
 
console_handler = logging.StreamHandler()
 
console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d" 
 
                                  " %(levelname)-5.5s [%(name)s] %(message)s"))
 
log.addHandler(console_handler)
 

	
 
class DbManage(object):
 
@@ -70,51 +70,105 @@ class DbManage(object):
 
        if override:
 
            log.info("database exisist and it's going to be destroyed")
 
            if self.db_exists:
 
                os.remove(jn(ROOT, self.dbname))
 
        Base.metadata.create_all(checkfirst=override)
 
        log.info('Created tables for %s', self.dbname)
 
    
 
    def admin_prompt(self):
 
        import getpass
 
        username = raw_input('Specify admin username:')
 
        password = getpass.getpass('Specify admin password:')
 
        self.create_user(username, password, True)
 
    
 
    def config_prompt(self):
 
        log.info('Seting up repositories.config')
 
        
 
        
 
        path = raw_input('Specify valid full path to your repositories'
 
                        ' you can change this later application settings:')
 
        
 
        if not os.path.isdir(path):
 
            log.error('You entered wrong path')
 
            sys.exit()
 
        
 
        hooks = HgAppUi()
 
        hooks.ui_section = 'hooks'
 
        hooks.ui_key = 'changegroup'
 
        hooks.ui_value = 'hg update >&2'
 
        
 
        web1 = HgAppUi()
 
        web1.ui_section = 'web'
 
        web1.ui_key = 'push_ssl'
 
        web1.ui_value = 'false'
 
                
 
        web2 = HgAppUi()
 
        web2.ui_section = 'web'
 
        web2.ui_key = 'allow_archive'
 
        web2.ui_value = 'gz zip bz2'
 
                
 
        web3 = HgAppUi()
 
        web3.ui_section = 'web'
 
        web3.ui_key = 'allow_push'
 
        web3.ui_value = '*'
 
        
 
        web4 = HgAppUi()
 
        web4.ui_section = 'web'
 
        web4.ui_key = 'baseurl'
 
        web4.ui_value = '/'                        
 
        
 
        paths = HgAppUi()
 
        paths.ui_section = 'paths'
 
        paths.ui_key = '/'
 
        paths.ui_value = os.path.join(path, '*')
 
        
 
        
 
        try:
 
            self.sa.add(hooks)
 
            self.sa.add(web1)
 
            self.sa.add(web2)
 
            self.sa.add(web3)
 
            self.sa.add(web4)
 
            self.sa.add(paths)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise        
 
        log.info('created ui config')
 
                    
 
    def create_user(self, username, password, admin=False):
 
        
 
        log.info('creating default user')
 
        #create default user for handling default permissions.
 
        def_user = User()
 
        def_user.username = 'default'
 
        def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
 
        def_user.name = 'default'
 
        def_user.lastname = 'default'
 
        def_user.email = 'default@default.com'
 
        def_user.admin = False
 
        def_user.active = False
 
        
 
        self.sa.add(def_user)
 
        
 
        log.info('creating administrator user %s', username)
 
        new_user = User()
 
        new_user.username = username
 
        new_user.password = get_crypt_password(password)
 
        new_user.name = 'Hg'
 
        new_user.lastname = 'Admin'
 
        new_user.email = 'admin@localhost'
 
        new_user.admin = admin
 
        new_user.active = True
 
        
 
        try:
 
            self.sa.add(def_user)
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 
    
 
    def create_permissions(self):
 
        #module.(access|create|change|delete)_[name]
 
        #module.(read|write|owner)
 
        perms = [('repository.none', 'Repository no access'),
 
                 ('repository.read', 'Repository read access'),
 
                 ('repository.write', 'Repository write access'),
pylons_app/lib/middleware/simplehg.py
Show inline comments
 
@@ -41,25 +41,25 @@ import pylons_app.lib.helpers as h
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 
import logging
 
import os
 
import traceback
 
log = logging.getLogger(__name__)
 

	
 
class SimpleHg(object):
 

	
 
    def __init__(self, application, config):
 
        self.application = application
 
        self.config = config
 
        #authenticate this mercurial request using 
 
        realm = '%s %s' % (self.config['hg_app_name'], 'mercurial repository')
 
        realm = self.config['hg_app_auth_realm']
 
        self.authenticate = AuthBasicAuthenticator(realm, authfunc)
 
        
 
    def __call__(self, environ, start_response):
 
        if not is_mercurial(environ):
 
            return self.application(environ, start_response)
 
        else:
 
            #===================================================================
 
            # AUTHENTICATE THIS MERCURIAL REQUEST
 
            #===================================================================
 
            username = REMOTE_USER(environ)
 
            if not username:
 
                result = self.authenticate(environ)
 
@@ -102,32 +102,31 @@ class SimpleHg(object):
 
                        return HTTPForbidden()(environ, start_response)
 
                
 
                #log action    
 
                proxy_key = 'HTTP_X_REAL_IP'
 
                def_key = 'REMOTE_ADDR'
 
                ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
 
                self.__log_user_action(user, action, repo_name, ipaddr)            
 
            
 
            #===================================================================
 
            # MERCURIAL REQUEST HANDLING
 
            #===================================================================
 
            environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
 
            self.baseui = make_ui(self.config['hg_app_repo_conf'])
 
            self.baseui = make_ui('db')
 
            self.basepath = self.config['base_path']
 
            self.repo_path = os.path.join(self.basepath, repo_name)
 

	
 
            #quick check if that dir exists...
 
            if check_repo_fast(repo_name, self.basepath):
 
                return HTTPNotFound()(environ, start_response)
 
            
 
            try:
 
                app = wsgiapplication(self.__make_app)
 
            except RepoError as e:
 
                if str(e).find('not found') != -1:
 
                    return HTTPNotFound()(environ, start_response)
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                return HTTPInternalServerError()(environ, start_response)
 
            
 
            #invalidate cache on push
 
            if action == 'push':
 
                self.__invalidate_cache(repo_name)
 
@@ -146,25 +145,25 @@ class SimpleHg(object):
 
        from merurial protocol actions that involves remote answers
 
        @param app:
 
        @param environ:
 
        @param start_response:
 
        """
 
        def custom_messages(msg_list):
 
            for msg in msg_list:
 
                yield msg + '\n'
 
        org_response = app(environ, start_response)
 
        return chain(org_response, custom_messages(messages))
 

	
 
    def __make_app(self):
 
        hgserve = hgweb(self.repo_path)
 
        hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
 
        return  self.__load_web_settings(hgserve)
 
    
 
    def __get_environ_user(self, environ):
 
        return environ.get('REMOTE_USER')
 
    
 
    def __get_size(self, repo_path, content_size):
 
        size = int(content_size)
 
        for path, dirs, files in os.walk(repo_path):
 
            if path.find('.hg') == -1:
 
                for f in files:
 
                    size += os.path.getsize(os.path.join(path, f))
 
        return size
 
@@ -205,21 +204,23 @@ class SimpleHg(object):
 
            sa.rollback()
 
            log.error('could not log user action:%s', str(e))
 
    
 
    def __invalidate_cache(self, repo_name):
 
        """we know that some change was made to repositories and we should
 
        invalidate the cache to see the changes right away but only for
 
        push requests"""
 
        invalidate_cache('cached_repo_list')
 
        invalidate_cache('full_changelog', repo_name)
 
           
 
                   
 
    def __load_web_settings(self, hgserve):
 
        repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
 
        #set the global ui for hgserve
 
        hgserve.repo.ui = self.baseui
 
        
 
        hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
 
        repoui = make_ui('file', hgrc, False)
 
        
 
        if repoui:
 
            #set the repository based config
 
            hgserve.repo.ui = repoui
 
            
 
        return hgserve
pylons_app/lib/utils.py
Show inline comments
 
@@ -18,25 +18,25 @@
 
# MA  02110-1301, USA.
 

	
 
"""
 
Created on April 18, 2010
 
Utilities for hg app
 
@author: marcink
 
"""
 

	
 
import os
 
import logging
 
from mercurial import ui, config, hg
 
from mercurial.error import RepoError
 
from pylons_app.model.db import Repository, User
 
from pylons_app.model.db import Repository, User, HgAppUi
 
log = logging.getLogger(__name__)
 

	
 

	
 
def get_repo_slug(request):
 
    return request.environ['pylons.routes_dict'].get('repo_name')
 

	
 
def is_mercurial(environ):
 
    """
 
    Returns True if request's target is mercurial server - header
 
    ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
 
    """
 
    http_accept = environ.get('HTTP_ACCEPT')
 
@@ -66,71 +66,76 @@ def check_repo(repo_name, base_path, ver
 
            return False
 
        r = hg.repository(ui.ui(), repo_path)
 
        if verify:
 
            hg.verify(r)
 
        #here we hnow that repo exists it was verified
 
        log.info('%s repo is already created', repo_name)
 
        return False
 
    except RepoError:
 
        #it means that there is no valid repo there...
 
        log.info('%s repo is free for creation', repo_name)
 
        return True
 

	
 
def make_ui(path=None, checkpaths=True):        
 
def make_ui(read_from='file', path=None, checkpaths=True):        
 
    """
 
    A funcion that will read python rc files and make an ui from read options
 
    A function that will read python rc files or database
 
    and make an mercurial ui object from read options
 
    
 
    @param path: path to mercurial config file
 
    @param checkpaths: check the path
 
    @param read_from: read from 'file' or 'db'
 
    """
 
    if not path:
 
        log.error('repos config path is empty !')
 
    
 
    if not os.path.isfile(path):
 
        log.warning('Unable to read config file %s' % path)
 
        return False
 
    #propagated from mercurial documentation
 
    sections = [
 
                'alias',
 
                'auth',
 
                'decode/encode',
 
                'defaults',
 
                'diff',
 
                'email',
 
                'extensions',
 
                'format',
 
                'merge-patterns',
 
                'merge-tools',
 
                'hooks',
 
                'http_proxy',
 
                'smtp',
 
                'patch',
 
                'paths',
 
                'profiling',
 
                'server',
 
                'trusted',
 
                'ui',
 
                'web',
 
                ]
 
    sections = ['alias', 'auth',
 
                'decode/encode', 'defaults',
 
                'diff', 'email',
 
                'extensions', 'format',
 
                'merge-patterns', 'merge-tools',
 
                'hooks', 'http_proxy',
 
                'smtp', 'patch',
 
                'paths', 'profiling',
 
                'server', 'trusted',
 
                'ui', 'web', ]
 
    baseui = ui.ui()
 

	
 
    baseui = ui.ui()
 
    cfg = config.config()
 
    cfg.read(path)
 
    if checkpaths:check_repo_dir(cfg.items('paths'))
 

	
 
    for section in sections:
 
        for k, v in cfg.items(section):
 
            baseui.setconfig(section, k, v)
 
                
 
    if read_from == 'file':
 
        if not os.path.isfile(path):
 
            log.warning('Unable to read config file %s' % path)
 
            return False
 
        
 
        cfg = config.config()
 
        cfg.read(path)
 
        for section in sections:
 
            for k, v in cfg.items(section):
 
                baseui.setconfig(section, k, v)
 
        if checkpaths:check_repo_dir(cfg.items('paths'))                
 
              
 
        
 
    elif read_from == 'db':
 
        from pylons_app.model.meta import Session
 
        sa = Session()
 
            
 
        hg_ui = sa.query(HgAppUi).all()
 
        for ui_ in hg_ui:
 
            baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
 
        
 
    
 
    return baseui
 

	
 

	
 
def set_hg_app_config(config):
 
    config['hg_app_auth_realm'] = 'realm'
 
    config['hg_app_name'] = 'app name'
 

	
 
def invalidate_cache(name, *args):
 
    """Invalidates given name cache"""
 
    
 
    from beaker.cache import region_invalidate
 
    log.info('INVALIDATING CACHE FOR %s', name)
 
    
 
    """propagate our arguments to make sure invalidation works. First
 
    argument has to be the name of cached func name give to cache decorator
 
    without that the invalidation would not work"""
 
    tmp = [name]
 
    tmp.extend(args)
 
    args = tuple(tmp)
pylons_app/model/db.py
Show inline comments
 
from pylons_app.model.meta import Base
 
from sqlalchemy.orm import relation, backref
 
from sqlalchemy import *
 
from vcs.utils.lazy import LazyProperty
 

	
 
class HgAppSettings(Base):
 
    __tablename__ = 'hg_app_settings'
 
    __table_args__ = {'useexisting':True}
 
    app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
 
    app_title = Column("app_title", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    app_auth_realm = Column("auth_realm", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
class HgAppUi(Base):
 
    __tablename__ = 'hg_app_ui'
 
    __table_args__ = {'useexisting':True}
 
    ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
 
    ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
class User(Base): 
 
    __tablename__ = 'users'
 
    __table_args__ = {'useexisting':True}
 
    user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
 
    username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
 
    admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
 
    name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
pylons_app/websetup.py
Show inline comments
 
@@ -3,47 +3,21 @@
 
from os.path import dirname as dn, join as jn
 
from pylons_app.config.environment import load_environment
 
from pylons_app.lib.db_manage import DbManage
 
import logging
 
import os
 
import sys
 

	
 
log = logging.getLogger(__name__)
 

	
 
ROOT = dn(dn(os.path.realpath(__file__)))
 
sys.path.append(ROOT)
 

	
 

	
 
def setup_repository():
 
    log.info('Seting up repositories.config')
 
    fname = 'repositories.config'
 
    
 
    try:
 
        tmpl = open(jn(ROOT, 'pylons_app', 'config', 'repositories.config_tmpl')).read()
 
    except IOError:
 
        raise
 
    
 
    path = raw_input('Specify valid full path to your repositories'
 
                    ' you can change this later in repositories.config file:')
 
    
 
    if not os.path.isdir(path):
 
        log.error('You entered wrong path')
 
        sys.exit()
 
    
 
    
 
    path = jn(path, '*') 
 
    dest_path = jn(ROOT, fname)
 
    f = open(dest_path, 'wb')
 
    f.write(tmpl % {'repo_location':path})
 
    f.close()
 
    log.info('created repositories.config in %s', dest_path)
 
        
 

	
 
def setup_app(command, conf, vars):
 
    """Place any commands to setup pylons_app here"""
 
    setup_repository()
 
    dbmanage = DbManage(log_sql=True)
 
    dbmanage.create_tables(override=True)
 
    dbmanage.config_prompt()
 
    dbmanage.admin_prompt()
 
    dbmanage.create_permissions()
 
    load_environment(conf.global_conf, conf.local_conf)
 

	
0 comments (0 inline, 0 general)