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