goa/tools/laxctl.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 06 Aug 2010 17:38:45 +0200
branchstable
changeset 6076 aa7402e998a1
parent 5467 57372dbfd114
permissions -rw-r--r--
[migration] when some inlined relation definition is added to an entity type which has some specialized types, it should be propagated to those types else they may miss the necessary table column to store the relation

# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
"""provides all lax instances management commands into a single utility script

"""
__docformat__ = "restructuredtext en"

import sys
import os
import os.path as osp
import time
import re
import urllib2
from urllib import urlencode
from Cookie import SimpleCookie

from logilab.common.clcommands import Command, register_commands, main_run

from cubicweb.uilib import remove_html_tags
from cubicweb.web.views.schema import SKIP_TYPES

APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))


def initialize_vregistry(applroot):
    # apply monkey patches first
    from cubicweb.goa import do_monkey_patch
    do_monkey_patch()
    from cubicweb.goa.goavreg import GAEVregistry
    from cubicweb.goa.goaconfig import GAEConfiguration
    #WebConfiguration.uiprops['JAVASCRIPTS'].append('DATADIR/goa.js')
    config = GAEConfiguration('toto', applroot)
    vreg = GAEVregistry(config)
    vreg.set_schema(config.load_schema())
    return vreg

def alistdir(directory):
    return [osp.join(directory, f) for f in os.listdir(directory)]


class LaxCommand(Command):
    """base command class for all lax commands
    creates vreg, schema and calls
    """
    min_args = max_args = 0

    def run(self, args):
        self.vreg = initialize_vregistry(APPLROOT)
        self._run(args)


class GenerateSchemaCommand(LaxCommand):
    """generates the schema's png file"""
    name = 'genschema'

    def _run(self, args):
        assert not args, 'no argument expected'
        from yams import schema2dot
        schema = self.vreg.schema
        path = osp.join(APPLROOT, 'data', 'schema.png')
        schema2dot.schema2dot(schema, path, #size=size,
                              skiptypes=SKIP_TYPES)
        print 'generated', path
        path = osp.join(APPLROOT, 'data', 'metaschema.png')
        schema2dot.schema2dot(schema, path)
        print 'generated', path


class PopulateDataDirCommand(LaxCommand):
    """populate instance's data directory according to used cubes"""
    name = 'populatedata'

    def _run(self, args):
        assert not args, 'no argument expected'
        # first clean everything which is a symlink from the data directory
        datadir = osp.join(APPLROOT, 'data')
        if not osp.exists(datadir):
            print 'created data directory'
            os.mkdir(datadir)
        for filepath in alistdir(datadir):
            if osp.islink(filepath):
                print 'removing', filepath
                os.remove(filepath)
        cubes = list(self.vreg.config.cubes()) + ['shared']
        for templ in cubes:
            templpath = self.vreg.config.cube_dir(templ)
            templdatadir = osp.join(templpath, 'data')
            if not osp.exists(templdatadir):
                print 'no data provided by', templ
                continue
            for resource in os.listdir(templdatadir):
                if resource == 'external_resources':
                    continue
                if not osp.exists(osp.join(datadir, resource)):
                    print 'symlinked %s from %s' % (resource, templ)
                    os.symlink(osp.join(templdatadir, resource),
                               osp.join(datadir, resource))


class NoRedirectHandler(urllib2.HTTPRedirectHandler):
    def http_error_302(self, req, fp, code, msg, headers):
        raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
    http_error_301 = http_error_303 = http_error_307 = http_error_302


class GetSessionIdHandler(urllib2.HTTPRedirectHandler):
    def __init__(self, config):
        self.config = config

    def http_error_303(self, req, fp, code, msg, headers):
        cookie = SimpleCookie(headers['Set-Cookie'])
        sessionid = cookie['__session'].value
        print 'session id', sessionid
        setattr(self.config, 'cookie', '__session=' + sessionid)
        return 1 # on exception should be raised


class URLCommand(LaxCommand):
    """abstract class for commands doing stuff by accessing the web instance
    """
    min_args = max_args = 1
    arguments = '<site url>'

    options = (
        ('cookie',
         {'short': 'C', 'type' : 'string', 'metavar': 'key=value',
          'default': None,
          'help': 'session/authentication cookie.'}),
        ('user',
         {'short': 'u', 'type' : 'string', 'metavar': 'login',
          'default': None,
          'help': 'user login instead of giving raw cookie string (require lax '
          'based authentication).'}),
        ('password',
         {'short': 'p', 'type' : 'string', 'metavar': 'password',
          'default': None,
          'help': 'user password instead of giving raw cookie string (require '
          'lax based authentication).'}),
        )

    def _run(self, args):
        baseurl = args[0]
        if not baseurl.startswith('http'):
            baseurl = 'http://' + baseurl
        if not baseurl.endswith('/'):
            baseurl += '/'
        self.base_url = baseurl
        if not self.config.cookie and self.config.user:
            # no cookie specified but a user is. Try to open a session using
            # given authentication info
            print 'opening session for', self.config.user
            opener = urllib2.build_opener(GetSessionIdHandler(self.config))
            urllib2.install_opener(opener)
            data = urlencode(dict(__login=self.config.user,
                                  __password=self.config.password))
            self.open_url(urllib2.Request(baseurl, data))
        opener = urllib2.build_opener(NoRedirectHandler())
        urllib2.install_opener(opener)
        self.do_base_url(baseurl)

    def build_req(self, url):
        req = urllib2.Request(url)
        if self.config.cookie:
            req.headers['Cookie'] = self.config.cookie
        return req

    def open_url(self, req):
        try:
            return urllib2.urlopen(req)
        except urllib2.HTTPError, ex:
            if ex.code == 302:
                self.error_302(req, ex)
            elif ex.code == 500:
                self.error_500(req, ex)
            else:
                raise

    def error_302(self, req, ex):
        print 'authentication required'
        print ('visit %s?vid=authinfo with your browser to get '
               'authentication info' % self.base_url)
        sys.exit(1)

    def error_500(self, req, ex):
        print 'an unexpected error occured on the server'
        print ('you may get more information by visiting '
               '%s' % req.get_full_url())
        sys.exit(1)

    def extract_message(self, data):
        match = re.search(r'<div class="message">(.*?)</div>', data.read(), re.M|re.S)
        if match:
            msg = remove_html_tags(match.group(1))
            print msg
            return msg

    def do_base_url(self, baseurl):
        raise NotImplementedError()


class DSInitCommand(URLCommand):
    """initialize the datastore"""
    name = 'db-init'

    options = URLCommand.options + (
        ('sleep',
         {'short': 's', 'type' : 'int', 'metavar': 'nb seconds',
          'default': None,
          'help': 'number of seconds to wait between each request to avoid '
          'going out of quota.'}),
        )

    def do_base_url(self, baseurl):
        req = self.build_req(baseurl + '?vid=contentinit')
        while True:
            try:
                data = self.open_url(req)
            except urllib2.HTTPError, ex:
                if ex.code == 303: # redirect
                    print 'process completed'
                    break
                raise
            msg = self.extract_message(data)
            if msg and msg.startswith('error: '):
                print ('you may to cleanup datastore by visiting '
                       '%s?vid=contentclear (ALL ENTITIES WILL BE DELETED)'
                       % baseurl)
                break
            if self.config.sleep:
                time.sleep(self.config.sleep)


class CleanSessionsCommand(URLCommand):
    """cleanup sessions on the server. This command should usually be called
    regularly by a cron job or equivalent.
    """
    name = "cleansessions"
    def do_base_url(self, baseurl):
        req = self.build_req(baseurl + '?vid=cleansessions')
        data = self.open_url(req)
        self.extract_message(data)


register_commands([GenerateSchemaCommand,
                   PopulateDataDirCommand,
                   DSInitCommand,
                   CleanSessionsCommand,
                   ])

def run():
    main_run(sys.argv[1:])

if __name__ == '__main__':
    run()