goa/tools/laxctl.py
branchstable
changeset 6340 470d8e828fda
parent 6339 bdc3dc94d744
child 6341 ad5e08981153
equal deleted inserted replaced
6339:bdc3dc94d744 6340:470d8e828fda
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """provides all lax instances management commands into a single utility script
       
    19 
       
    20 """
       
    21 __docformat__ = "restructuredtext en"
       
    22 
       
    23 import sys
       
    24 import os
       
    25 import os.path as osp
       
    26 import time
       
    27 import re
       
    28 import urllib2
       
    29 from urllib import urlencode
       
    30 from Cookie import SimpleCookie
       
    31 
       
    32 from logilab.common.clcommands import Command, register_commands, main_run
       
    33 
       
    34 from cubicweb.uilib import remove_html_tags
       
    35 from cubicweb.web.views.schema import SKIP_TYPES
       
    36 
       
    37 APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
       
    38 
       
    39 
       
    40 def initialize_vregistry(applroot):
       
    41     # apply monkey patches first
       
    42     from cubicweb.goa import do_monkey_patch
       
    43     do_monkey_patch()
       
    44     from cubicweb.goa.goavreg import GAEVregistry
       
    45     from cubicweb.goa.goaconfig import GAEConfiguration
       
    46     #WebConfiguration.uiprops['JAVASCRIPTS'].append('DATADIR/goa.js')
       
    47     config = GAEConfiguration('toto', applroot)
       
    48     vreg = GAEVregistry(config)
       
    49     vreg.set_schema(config.load_schema())
       
    50     return vreg
       
    51 
       
    52 def alistdir(directory):
       
    53     return [osp.join(directory, f) for f in os.listdir(directory)]
       
    54 
       
    55 
       
    56 class LaxCommand(Command):
       
    57     """base command class for all lax commands
       
    58     creates vreg, schema and calls
       
    59     """
       
    60     min_args = max_args = 0
       
    61 
       
    62     def run(self, args):
       
    63         self.vreg = initialize_vregistry(APPLROOT)
       
    64         self._run(args)
       
    65 
       
    66 
       
    67 class GenerateSchemaCommand(LaxCommand):
       
    68     """generates the schema's png file"""
       
    69     name = 'genschema'
       
    70 
       
    71     def _run(self, args):
       
    72         assert not args, 'no argument expected'
       
    73         from yams import schema2dot
       
    74         schema = self.vreg.schema
       
    75         path = osp.join(APPLROOT, 'data', 'schema.png')
       
    76         schema2dot.schema2dot(schema, path, #size=size,
       
    77                               skiptypes=SKIP_TYPES)
       
    78         print 'generated', path
       
    79         path = osp.join(APPLROOT, 'data', 'metaschema.png')
       
    80         schema2dot.schema2dot(schema, path)
       
    81         print 'generated', path
       
    82 
       
    83 
       
    84 class PopulateDataDirCommand(LaxCommand):
       
    85     """populate instance's data directory according to used cubes"""
       
    86     name = 'populatedata'
       
    87 
       
    88     def _run(self, args):
       
    89         assert not args, 'no argument expected'
       
    90         # first clean everything which is a symlink from the data directory
       
    91         datadir = osp.join(APPLROOT, 'data')
       
    92         if not osp.exists(datadir):
       
    93             print 'created data directory'
       
    94             os.mkdir(datadir)
       
    95         for filepath in alistdir(datadir):
       
    96             if osp.islink(filepath):
       
    97                 print 'removing', filepath
       
    98                 os.remove(filepath)
       
    99         cubes = list(self.vreg.config.cubes()) + ['shared']
       
   100         for templ in cubes:
       
   101             templpath = self.vreg.config.cube_dir(templ)
       
   102             templdatadir = osp.join(templpath, 'data')
       
   103             if not osp.exists(templdatadir):
       
   104                 print 'no data provided by', templ
       
   105                 continue
       
   106             for resource in os.listdir(templdatadir):
       
   107                 if resource == 'external_resources':
       
   108                     continue
       
   109                 if not osp.exists(osp.join(datadir, resource)):
       
   110                     print 'symlinked %s from %s' % (resource, templ)
       
   111                     os.symlink(osp.join(templdatadir, resource),
       
   112                                osp.join(datadir, resource))
       
   113 
       
   114 
       
   115 class NoRedirectHandler(urllib2.HTTPRedirectHandler):
       
   116     def http_error_302(self, req, fp, code, msg, headers):
       
   117         raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
       
   118     http_error_301 = http_error_303 = http_error_307 = http_error_302
       
   119 
       
   120 
       
   121 class GetSessionIdHandler(urllib2.HTTPRedirectHandler):
       
   122     def __init__(self, config):
       
   123         self.config = config
       
   124 
       
   125     def http_error_303(self, req, fp, code, msg, headers):
       
   126         cookie = SimpleCookie(headers['Set-Cookie'])
       
   127         sessionid = cookie['__session'].value
       
   128         print 'session id', sessionid
       
   129         setattr(self.config, 'cookie', '__session=' + sessionid)
       
   130         return 1 # on exception should be raised
       
   131 
       
   132 
       
   133 class URLCommand(LaxCommand):
       
   134     """abstract class for commands doing stuff by accessing the web instance
       
   135     """
       
   136     min_args = max_args = 1
       
   137     arguments = '<site url>'
       
   138 
       
   139     options = (
       
   140         ('cookie',
       
   141          {'short': 'C', 'type' : 'string', 'metavar': 'key=value',
       
   142           'default': None,
       
   143           'help': 'session/authentication cookie.'}),
       
   144         ('user',
       
   145          {'short': 'u', 'type' : 'string', 'metavar': 'login',
       
   146           'default': None,
       
   147           'help': 'user login instead of giving raw cookie string (require lax '
       
   148           'based authentication).'}),
       
   149         ('password',
       
   150          {'short': 'p', 'type' : 'string', 'metavar': 'password',
       
   151           'default': None,
       
   152           'help': 'user password instead of giving raw cookie string (require '
       
   153           'lax based authentication).'}),
       
   154         )
       
   155 
       
   156     def _run(self, args):
       
   157         baseurl = args[0]
       
   158         if not baseurl.startswith('http'):
       
   159             baseurl = 'http://' + baseurl
       
   160         if not baseurl.endswith('/'):
       
   161             baseurl += '/'
       
   162         self.base_url = baseurl
       
   163         if not self.config.cookie and self.config.user:
       
   164             # no cookie specified but a user is. Try to open a session using
       
   165             # given authentication info
       
   166             print 'opening session for', self.config.user
       
   167             opener = urllib2.build_opener(GetSessionIdHandler(self.config))
       
   168             urllib2.install_opener(opener)
       
   169             data = urlencode(dict(__login=self.config.user,
       
   170                                   __password=self.config.password))
       
   171             self.open_url(urllib2.Request(baseurl, data))
       
   172         opener = urllib2.build_opener(NoRedirectHandler())
       
   173         urllib2.install_opener(opener)
       
   174         self.do_base_url(baseurl)
       
   175 
       
   176     def build_req(self, url):
       
   177         req = urllib2.Request(url)
       
   178         if self.config.cookie:
       
   179             req.headers['Cookie'] = self.config.cookie
       
   180         return req
       
   181 
       
   182     def open_url(self, req):
       
   183         try:
       
   184             return urllib2.urlopen(req)
       
   185         except urllib2.HTTPError, ex:
       
   186             if ex.code == 302:
       
   187                 self.error_302(req, ex)
       
   188             elif ex.code == 500:
       
   189                 self.error_500(req, ex)
       
   190             else:
       
   191                 raise
       
   192 
       
   193     def error_302(self, req, ex):
       
   194         print 'authentication required'
       
   195         print ('visit %s?vid=authinfo with your browser to get '
       
   196                'authentication info' % self.base_url)
       
   197         sys.exit(1)
       
   198 
       
   199     def error_500(self, req, ex):
       
   200         print 'an unexpected error occured on the server'
       
   201         print ('you may get more information by visiting '
       
   202                '%s' % req.get_full_url())
       
   203         sys.exit(1)
       
   204 
       
   205     def extract_message(self, data):
       
   206         match = re.search(r'<div class="message">(.*?)</div>', data.read(), re.M|re.S)
       
   207         if match:
       
   208             msg = remove_html_tags(match.group(1))
       
   209             print msg
       
   210             return msg
       
   211 
       
   212     def do_base_url(self, baseurl):
       
   213         raise NotImplementedError()
       
   214 
       
   215 
       
   216 class DSInitCommand(URLCommand):
       
   217     """initialize the datastore"""
       
   218     name = 'db-init'
       
   219 
       
   220     options = URLCommand.options + (
       
   221         ('sleep',
       
   222          {'short': 's', 'type' : 'int', 'metavar': 'nb seconds',
       
   223           'default': None,
       
   224           'help': 'number of seconds to wait between each request to avoid '
       
   225           'going out of quota.'}),
       
   226         )
       
   227 
       
   228     def do_base_url(self, baseurl):
       
   229         req = self.build_req(baseurl + '?vid=contentinit')
       
   230         while True:
       
   231             try:
       
   232                 data = self.open_url(req)
       
   233             except urllib2.HTTPError, ex:
       
   234                 if ex.code == 303: # redirect
       
   235                     print 'process completed'
       
   236                     break
       
   237                 raise
       
   238             msg = self.extract_message(data)
       
   239             if msg and msg.startswith('error: '):
       
   240                 print ('you may to cleanup datastore by visiting '
       
   241                        '%s?vid=contentclear (ALL ENTITIES WILL BE DELETED)'
       
   242                        % baseurl)
       
   243                 break
       
   244             if self.config.sleep:
       
   245                 time.sleep(self.config.sleep)
       
   246 
       
   247 
       
   248 class CleanSessionsCommand(URLCommand):
       
   249     """cleanup sessions on the server. This command should usually be called
       
   250     regularly by a cron job or equivalent.
       
   251     """
       
   252     name = "cleansessions"
       
   253     def do_base_url(self, baseurl):
       
   254         req = self.build_req(baseurl + '?vid=cleansessions')
       
   255         data = self.open_url(req)
       
   256         self.extract_message(data)
       
   257 
       
   258 
       
   259 register_commands([GenerateSchemaCommand,
       
   260                    PopulateDataDirCommand,
       
   261                    DSInitCommand,
       
   262                    CleanSessionsCommand,
       
   263                    ])
       
   264 
       
   265 def run():
       
   266     main_run(sys.argv[1:])
       
   267 
       
   268 if __name__ == '__main__':
       
   269     run()