root/cleverbox/trunk/cleverbox/scripts/admin.py

Revision 353, 16.6 KB (checked in by trivoallan, 3 years ago)

Support for custom trac-admin location.

Line 
1# -*- coding: utf-8 -*-
2
3# This file is part of the "Cleverbox" program.
4#
5# Cleverbox is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Cleverbox is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Cleverbox.  If not, see <http://www.gnu.org/licenses/>.
17#
18# Copyright 2008, Tristan Rivoallan
19
20import cmd, logging, os, shlex, sys, traceback
21from trac import util
22from trac.admin.console import TracAdmin
23from cleverbox.utils import termcolors, styles, inputcollector
24from cleverbox.model.environment import Environment
25from cleverbox.model import client, project
26import cleverbox.log
27
28class CleverboxAdmin(cmd.Cmd):
29    intro = ''
30    license = ''
31    doc_header = 'Cleverbox Admin Console \n' \
32                 'Available Commands:\n'
33    ruler = ''
34    prompt = styles.style.H1("Cleverbox > ")
35    _date_format = '%Y-%m-%d'
36    _datetime_format = '%Y-%m-%d %H:%M:%S'
37    _date_format_hint = 'YYYY-MM-DD'
38
39    _config = None
40
41    def __init__(self, envdir = None):
42        cmd.Cmd.__init__(self)
43        if envdir:
44            self.env_set(os.path.abspath(envdir))
45        self.interactive = False
46
47        # Setup logging
48        screen = logging.StreamHandler(sys.stdout)
49        screen.setFormatter(cleverbox.log.TermcolorsFormatter('%(message)s'))
50        logging.getLogger('').setLevel(logging.DEBUG)
51        logging.getLogger('').addHandler(screen)
52
53    ##
54    ## Environment methods
55    ##
56
57    def env_set(self, env_path, check_upgrade=True):
58
59        # Load environment
60        self.env = Environment(env_path)
61
62        try:
63            # Cleverbox cannot run with trac-0.10.3
64            import trac
65            if trac.__version__ == '0.10.3':
66                print
67                print "A bug in trac-0.10.3 prevents the cleverbox from working correctly."
68                print "Please upgrade or downgrade your trac installation."
69                print
70               
71                sys.exit(1)
72               
73            # Check if environment needs an upgrade           
74            if check_upgrade and self.env.needs_upgrade(cleverbox.version):
75                print
76                logging.error("  Cleverbox environment needs to be upgraded.\n" \
77                              "  Please run : cleverbox-admin %s upgrade" % self.env.path)
78                print
79                sys.exit(1)
80            elif not self.env.needs_upgrade(cleverbox.version):
81                print
82                logging.info(styles.style.SUCCESS("  Cleverbox environment is up to date."))
83                print
84        except IOError, e:
85            # no VERSION file means user wants to create an environment
86            print
87            logging.warn("  Environment at %s has not been initialized yet.\n" \
88                         "  Please run the 'initenv' command." % self.env.path)
89            print
90
91        # Set shell prompt
92        self.prompt = "Cleverbox [%s] > " % self.env.path
93
94    def emptyline(self):
95        pass
96
97    def onecmd(self, line):
98        try:
99            rv = cmd.Cmd.onecmd(self, line) or 0
100        except SystemExit:
101            raise
102        except Exception, e:
103            print
104            logging.error('  Command failed: %s' % e)
105            logging.debug(traceback.print_exc())
106            print
107            rv = 2
108        if not self.interactive:
109            return rv
110
111    def run(self):
112        self.interactive = True
113        print styles.style.H1('Welcome to cleverbox-admin v%s\n' \
114                              'Interactive Cleverbox administration console.\n' \
115                              "Type:  '?' or 'help' for help on commands.\n") % self.env.get_version()
116        self.cmdloop()
117
118    ## Environment
119    _help_initenv = [('initenv', 'Environment initialisation')]
120    def do_initenv(self, line=None):
121        logging.info("Environment initialisation in %(env_dir)s" % {'env_dir' : self.env.path})
122
123        #Collect local configuration info
124        collected_infos = {'general' : {}, 'trac' : {}}
125
126        # Path to client dir
127        default_root = '/var/cleverbox'
128        collected_infos['general']['clients_root'] = raw_input('Projects directory [%s]> ' % default_root).strip() or default_root
129
130        # Path to cleverbox assets
131        default_assets_dir = '/usr/share/cleverbox'
132        collected_infos['general']['assets_dir'] = raw_input('Cleverbox assets directory [%s]> ' % default_assets_dir).strip() or default_assets_dir
133
134        # Apache user & group
135        d_uid = 'www-data'
136        d_gid = 'www-data'
137        collected_infos['general']['apache_user'] = raw_input('Webserver user [%s]> ' % d_uid).strip() or d_uid
138        collected_infos['general']['apache_group'] = raw_input('Webserver group [%s]> ' % d_gid).strip() or d_gid
139
140        d_root_gid = 'www-data'
141        collected_infos['general']['root_group'] = raw_input('Root group [%s]> ' % d_root_gid).strip() or d_root_gid
142
143        # root user & group
144        # we keep the ssh_user notion for backward compatibility.
145        # this will have to disappear in a future release
146        collected_infos['general']['ssh_user'] = 'root'
147        collected_infos['general']['ssh_group'] = 'root'
148
149        # Host server domain name
150        collected_infos['general']['domain'] = raw_input('Domain name > ').strip()
151
152        # Authentication backend password (if any)
153        collected_infos['general']['authbackend_pass'] = raw_input('Authentication backend password (if any) []> ').strip() or ''
154
155        # Default configuration profile
156        dcp = 'default'
157        collected_infos['general']['default_profile'] = raw_input('Default configuration profile [%s]> ' % dcp).strip() or dcp
158
159        d_tracadmin_path = '/usr/bin/trac-admin'
160        collected_infos['trac']['tracadmin_path'] = raw_input('Path to trac-admin executable [%s]> ' % d_tracadmin_path).strip() or d_tracadmin_path
161
162        # Environment creation
163        self.env.create(cleverbox.version, collected_infos)
164
165        print
166        print termcolors.colorize('', fg='green', opts=('noreset',))
167        logging.info("Environment successfully initialized\n" \
168                     "You need to add this statement to your apache configuration : \n" \
169                     "\t'Include %(env_dir)s/clients-enabled/*'\n" % {'env_dir' : self.env.path})
170        print termcolors.colorize('')
171
172    _help_upgrade = [('upgrade', 'Executes necessary operation to make environment up to date')]
173    def do_upgrade(self, line=None):
174        self.env.upgrade()
175
176    ## help
177    def do_help(self, line=None):
178        arg = self._arg_tokenize(line)
179
180        if arg[0]:
181            try:
182                doc = getattr(self, "_help_" + arg[0])
183                TracAdmin.print_doc(doc)
184            except AttributeError:
185                logging.error("No documentation found for %s" % arg[0])
186        else:
187            docs = (self._help_client + self._help_project + self._help_initenv + self._help_upgrade)
188            print 'cleverbox-admin - The Cleverbox Administration Console v%s' % self.env.get_version()
189            if not self.interactive:
190                print
191                print "Usage: cleverbox-admin env_dir [command [subcommand] [option ...]]\n"
192                print "Invoking cleverbox-admin without command starts "\
193                      "interactive mode."
194            TracAdmin.print_doc(docs)
195
196
197    #
198    # "Client" command set
199    #
200    _help_client = [('client list', 'Lists available clients'),
201                    ('client add <name>', 'Adds a client'),
202                    ('client enable <name>', 'Enables a client'),
203                    ('client disable <name>', 'Disables a client')]
204    def do_client(self, line):
205        args = self._arg_tokenize(line)
206        if args[0] == 'list':
207            self._do_client_list()
208        elif args[0] == 'add' and len(args) == 2:
209            self._do_client_add(args[1])
210        elif args[0] == 'remove' and len(args) == 2:
211            self._do_client_remove(args[1])
212        elif args[0] == 'enable' and len(args) == 2:
213            self._do_client_enable(args[1])
214        elif args[0] == 'disable' and len(args) == 2:
215            self._do_client_disable(args[1])
216        else:
217            self.do_help('client')
218
219
220    def complete_client(self, text, line, begidx, endidx):
221        '''
222        Completions for the client command.
223        Each client subcommand has its own completion command, named _complete_client_<subcommand>
224        '''
225
226        comp = ['list', 'add', 'remove', 'enable', 'disable']
227        parts = self._arg_tokenize(line)
228
229        if len(parts) > 1 and parts[1] in comp:
230            complete_func = getattr(self, '_complete_client_' + parts[1])
231            if (float(sys.version[:3]) < 2.4):
232                comp = complete_func.__call__(self)
233            else:
234                comp = complete_func.__call__()
235
236        return self.word_complete(text, comp)
237
238
239    def _do_client_list(self):
240        '''
241        Displays enabled and disabled clients.
242        '''
243
244        all_clients = client.get(self.env)
245        if len(all_clients):
246            print
247            all_clients.sort()
248            en_clients = client.get(self.env, 'enabled')
249            dis_clients = client.get(self.env, 'disabled')
250            for client_name in all_clients:
251                if client_name in en_clients:
252                    print styles.style.ENABLED('  [E] %s' % client_name)
253                else:
254                    print styles.style.DISABLED('  [D] %s' % client_name)
255            print
256
257    def _do_client_add(self, client_name):
258        client.add(self.env, client_name)
259
260    def _do_client_enable(self, client_name):
261        client.enable(self.env, client_name)
262
263    def _do_client_disable(self, client_name):
264        client.disable(self.env, client_name)
265
266    def _complete_client_remove(self):
267        return client.get(self.env)
268
269    def _complete_client_enable(self):
270        return client.get(self.env, 'disabled')
271
272    def _complete_client_disable(self):
273        return client.get(self.env, 'enabled')
274
275    #
276    # Project command set
277    #
278
279    _help_project = [('project list', 'Lists available projects'),
280                    ('project add <client> <name>', 'Adds a project'),
281                    ('project enable <client> <name>', 'Enables a project'),
282                    ('project disable <client> <name>', 'Disables a project')]
283    def do_project(self, line):
284        parts = self._arg_tokenize(line)
285        try:
286            do_func = getattr(self, '_do_project_' + parts[0])
287            # Python __call__() signature changed in Python2.4
288            do_func.__call__(parts[1:])
289        except AttributeError, e:
290            logging.error(e)
291            self.do_help('project')
292
293    def _do_project_list(self, line=None, misc=None):
294
295        client_projects = {}
296
297        clients = client.get(self.env)
298        clients.sort()
299
300        for client_name in clients:
301
302            all_projects = project.get(self.env, client_name)
303            if not len(all_projects): continue
304
305            all_projects.sort()
306
307            print styles.style.H1("\n%s :" % client_name)
308
309            enabled_projects = project.get(self.env, client_name, 'enabled')
310            disabled_projects = project.get(self.env, client_name, 'disabled')
311
312
313            for project_name in all_projects:
314                if project_name in enabled_projects:
315                    print styles.style.ENABLED("[E] %s" % project_name)
316                else:
317                    print styles.style.DISABLED("[D] %s" % project_name)
318
319    def _do_project_disable(self, args):
320
321        # Check syntax
322        if not len(args) == 2:
323            self.do_help('project')
324            raise Exception, 'Invalid syntax'
325
326        # Disable project
327        (client_name, project_name) = args
328        project.disable(self.env, client_name, project_name)
329
330
331    def _do_project_enable(self, args):
332        if not len(args) == 2:
333            self.do_help('project')
334            raise Exception, 'Invalid syntax'
335
336        (client_name, project_name) = args
337        project.enable(self.env, client_name, project_name)
338
339    def _do_project_add(self, args):
340        if not len(args) == 2:
341            self.do_help('project')
342            raise Exception, 'Invalid syntax'
343
344        # Extract parameters from command parameters
345        (client_name, project_name) = args
346
347        # Create input specification
348        input_spec = {'full_name' : ('Full name', 'This variable is not used anywhere !', project_name.capitalize()),
349                      'profile'   : ('Configuration profile', 'What configuration profile should be used ?', self.env.config.get('general', 'default_profile')),
350                      'enable'    : ('Enable project ?', 'This will make project available on the web.', 'n'),
351                      'tracadmin' : ('Trac administrator', "This user will be granted full privileges on project's Trac instance", project_name + '-admin')}
352
353        # Collect input
354        collected_input = inputcollector.collect(input_spec)
355
356        # Create project
357        project.add(self.env, client_name, project_name, collected_input)
358
359    def complete_project(self, text, line, begidx, endidx):
360        comp = ['list', 'add', 'remove', 'enable', 'disable']
361        parts = self._arg_tokenize(line)
362
363        if len(parts) > 1 and parts[1] in comp:
364            complete_func = getattr(self, '_complete_project_' + parts[1])
365            if (float(sys.version[:3]) < 2.4):
366                comp = complete_func.__call__(self, parts[2:])
367            else:
368                comp = complete_func.__call__(parts[2:])
369
370        return self.word_complete(text, comp)
371
372    def _complete_project_add(self, args):
373        comp = []
374        # "add" subcommand's first argument is client's name
375        if not args or (len(args) <= 1 and args[0] not in client.get(self.env, args[0])):
376            comp = client.get(self.env)
377
378        return comp
379
380    def _complete_project_enable(self, args):
381        comp = []
382
383        # "enable" subcommand's first argument is client's name
384        if not args or (len(args) <= 1 and args[0] not in client.get(self.env)):
385            comp = client.get(self.env)
386        # "enable" subcommand second argument is project's name
387        else :
388            comp = project.get(self.env, args[0], 'disabled')
389
390        return comp
391
392    def _complete_project_disable(self, args):
393        comp = []
394
395        # "disable" subcommand's first argument is client's name
396        if not args or (len(args) <= 1 and args[0] not in client.get(self.env)):
397            comp = client.get(self.env)
398        # "disable" subcommand second argument is project's name
399        else :
400            comp = project.get(self.env, args[0], 'enabled')
401
402        return comp
403
404    def _complete_project_remove(self, args):
405        comp = []
406
407        # "remove" subcommand's first argument is client's name
408        if not args or (len(args) <= 1 and args[0] not in client.get(self.env)):
409            comp = client.get(self.env)
410        # "remove" subcommand second argument is project's name
411        else :
412            comp = project.get(self.env, args[0])
413
414        return comp
415
416    ## Quit / EOF
417    def do_quit(self, line):
418        print
419        sys.exit()
420
421    do_exit = do_quit # Alias
422    do_EOF = do_quit # Alias
423
424
425
426    #
427    # Helpers
428    #
429
430    def word_complete (self, text, words):
431        """
432        Word completion helper.
433        """
434        return [a for a in words if a.startswith (text)]
435
436    def _arg_tokenize (self, argstr):
437        """
438        Command line parameters tokenizer
439        """
440        argstr = util.to_utf8(argstr, sys.stdin.encoding)
441        return shlex.split(argstr) or ['']
442
443def run(args):
444    """
445    Main entry point.
446    """
447
448    admin = CleverboxAdmin()
449    if len(args) > 0:
450        if len(args) == 1:
451            while True:
452                admin.env_set(os.path.abspath(args[0]))
453                admin.run()
454        if len(args) > 1:
455            if args[1] in ('-h', '--help', 'help'):
456                return admin.onecmd("help")
457            elif args[1] in ('-v','--version','about'):
458                return admin.onecmd("about")
459            elif args[1] in ('-u', '--upgrade', 'upgrade'):
460                admin.env_set(args[0], False)
461                return admin.onecmd("upgrade")
462            else:
463                admin.env_set(os.path.abspath(args[0]))
464                if len(args) > 1:
465                    s_args = ' '.join(["'%s'" % command_parts for command_parts in args[2:]])
466                    command = args[1] + ' ' +s_args
467                    return admin.onecmd(command)
468    else:
469        return admin.onecmd("help")
470
Note: See TracBrowser for help on using the browser.