goa/tools/laxctl.py
changeset 0 b97547f5f1fa
child 1131 544609e83317
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/goa/tools/laxctl.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,289 @@
+"""provides all lax instances management commands into a single utility script
+
+:organization: Logilab
+:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__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 import CW_SOFTWARE_ROOT
+from cubicweb.common.uilib import remove_html_tags
+
+APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
+
+# XXX import custom?
+
+from tools import i18n
+
+def initialize_vregistry(applroot):
+    # apply monkey patches first
+    from cubicweb.goa import do_monkey_patch    
+    do_monkey_patch()
+    from cubicweb.goa.goavreg import GAERegistry
+    from cubicweb.goa.goaconfig import GAEConfiguration
+    #WebConfiguration.ext_resources['JAVASCRIPTS'].append('DATADIR/goa.js')
+    config = GAEConfiguration('toto', applroot)
+    vreg = GAERegistry(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 I18nUpdateCommand(LaxCommand):
+    """updates i18n catalogs"""
+    name = 'i18nupdate'
+    
+    def _run(self, args):
+        assert not args, 'no argument expected'
+        i18ndir = i18n.get_i18n_directory(APPLROOT)
+        i18n.update_cubes_catalog(self.vreg, APPLROOT,
+                                      langs=i18n.getlangs(i18ndir))
+
+
+class I18nCompileCommand(LaxCommand):
+    """compiles i18n catalogs"""
+    name = 'i18ncompile'
+    min_args = max_args = 0
+    
+    def _run(self, args):
+        assert not args, 'no argument expected'
+        i18ndir = i18n.get_i18n_directory(APPLROOT)
+        langs = i18n.getlangs(i18ndir)
+        print 'generating .mo files for langs', ', '.join(langs)
+        cubicweb_i18ndir = osp.join(APPLROOT, 'cubes', 'shared')
+        paths = self.vreg.config.cubes_path() + [cubicweb_i18ndir]
+        sourcedirs = [i18ndir] + [osp.join(path, 'i18n') for path in paths]
+        i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs=langs)
+        
+
+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
+        skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+        path = osp.join(APPLROOT, 'data', 'schema.png')
+        schema2dot.schema2dot(schema, path, #size=size,
+                              skiprels=skip_rels, skipmeta=True)
+        print 'generated', path
+        path = osp.join(APPLROOT, 'data', 'metaschema.png')
+        schema2dot.schema2dot(schema, path, #size=size,
+                              skiprels=skip_rels, skipmeta=False)
+        print 'generated', path
+
+
+class PopulateDataDirCommand(LaxCommand):
+    """populate application'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 application
+    """
+    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([I18nUpdateCommand,
+                   I18nCompileCommand,
+                   GenerateSchemaCommand,
+                   PopulateDataDirCommand,
+                   DSInitCommand,
+                   CleanSessionsCommand,
+                   ])
+
+def run():
+    main_run(sys.argv[1:])
+    
+if __name__ == '__main__':
+    run()