Edgewall Software

source: trunk/bitten/admin.py @ 1001

Last change on this file since 1001 was 910, checked in by osimons, 13 years ago

Updated copyright to 2010.

  • Property svn:eol-style set to native
File size: 14.6 KB
CovLine 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2007-2010 Edgewall Software
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://bitten.edgewall.org/wiki/License.
9
110"""Implementation of the web administration interface."""
11
112from pkg_resources import require, DistributionNotFound
113import re
14
115from trac.core import *
116from trac.admin import IAdminPanelProvider
117from trac.web.chrome import add_stylesheet, add_script, add_warning, add_notice
18
119from bitten.model import BuildConfig, TargetPlatform
120from bitten.recipe import Recipe, InvalidRecipeError
121from bitten.util import xmlio
22
23
224class BuildMasterAdminPageProvider(Component):
125    """Web administration panel for configuring the build master."""
26
127    implements(IAdminPanelProvider)
28
29    # IAdminPanelProvider methods
30
131    def get_admin_panels(self, req):
232        if req.perm.has_permission('BUILD_ADMIN'):
133            yield ('bitten', 'Builds', 'master', 'Master Settings')
34
135    def render_admin_panel(self, req, cat, page, path_info):
236        from bitten.master import BuildMaster
237        master = BuildMaster(self.env)
38
239        if req.method == 'POST':
140            self._save_config_changes(req, master)
141            req.redirect(req.abs_href.admin(cat, page))
42
143        data = {'master': master}
144        add_stylesheet(req, 'bitten/admin.css')
145        return 'bitten_admin_master.html', data
46
47    # Internal methods
48
149    def _save_config_changes(self, req, master):
150        changed = False
51
152        build_all = 'build_all' in req.args
153        if build_all != master.build_all:
054            self.config['bitten'].set('build_all',
055                                      build_all and 'yes' or 'no')
056            changed = True
57
158        adjust_timestamps = 'adjust_timestamps' in req.args
159        if adjust_timestamps != master.adjust_timestamps:
160            self.config['bitten'].set('adjust_timestamps',
161                                      adjust_timestamps and 'yes' or 'no')
162            changed = True
63
164        stabilize_wait = int(req.args.get('stabilize_wait', 0))
165        if stabilize_wait != master.stabilize_wait:
066            self.config['bitten'].set('stabilize_wait', str(stabilize_wait))
067            changed = True
68
169        slave_timeout = int(req.args.get('slave_timeout', 0))
170        if slave_timeout != master.slave_timeout:
171            self.config['bitten'].set('slave_timeout', str(slave_timeout))
172            changed = True
73
174        quick_status = 'quick_status' in req.args
175        if quick_status != master.quick_status:
076            self.config['bitten'].set('quick_status',
077                                      quick_status and 'yes' or 'no')
078            changed = True
79
180        logs_dir = req.args.get('logs_dir', None)
181        if logs_dir != master.logs_dir:
182            self.config['bitten'].set('logs_dir', str(logs_dir))
183            changed = True
84
185        if changed:
186            self.config.save()
87
188        return master
89
90
291class BuildConfigurationsAdminPageProvider(Component):
192    """Web administration panel for configuring the build master."""
93
194    implements(IAdminPanelProvider)
95
96    # IAdminPanelProvider methods
97
198    def get_admin_panels(self, req):
299        if req.perm.has_permission('BUILD_MODIFY'):
1100            yield ('bitten', 'Builds', 'configs', 'Configurations')
101
1102    def render_admin_panel(self, req, cat, page, path_info):
29103        data = {}
104
105        # Analyze url
29106        try:
29107            config_name, platform_id = path_info.split('/', 1)
26108        except:
26109            config_name = path_info
26110            platform_id = None
111
29112        if config_name: # Existing build config
14113            warnings = []
14114            if platform_id or (
115                    # Editing or creating one of the config's target platforms
11116                    req.method == 'POST' and 'new' in req.args):
117
5118                if platform_id: # Editing target platform
3119                    platform_id = int(platform_id)
3120                    platform = TargetPlatform.fetch(self.env, platform_id)
121
3122                    if req.method == 'POST':
2123                        if 'cancel' in req.args or \
1124                                self._update_platform(req, platform):
2125                            req.redirect(req.abs_href.admin(cat, page,
2126                                                            config_name))
2127                else: # creating target platform
2128                    platform = self._create_platform(req, config_name)
1129                    req.redirect(req.abs_href.admin(cat, page,
1130                                            config_name, platform.id))
131
132                # Set up template variables
1133                data['platform'] = {
1134                    'id': platform.id, 'name': platform.name,
1135                    'exists': platform.exists,
1136                    'rules': [
1137                        {'property': propname, 'pattern': pattern}
1138                        for propname, pattern in platform.rules
1139                    ] or [('', '')]
1140                }
141
0142            else: # Editing existing build config itself
9143                config = BuildConfig.fetch(self.env, config_name)
9144                platforms = list(TargetPlatform.select(self.env,
9145                                                       config=config.name))
146
9147                if req.method == 'POST':
8148                    if 'remove' in req.args: # Remove selected platforms
2149                        self._remove_platforms(req)
1150                        add_notice(req, "Target Platform(s) Removed.")
1151                        req.redirect(req.abs_href.admin(cat, page, config.name))
152
6153                    elif 'save' in req.args: # Save this build config
6154                        warnings = self._update_config(req, config)
155
6156                    if not warnings:
1157                        add_notice(req, "Configuration Saved.")
1158                        req.redirect(req.abs_href.admin(cat, page, config.name))
159
10160                    for warning in warnings:
5161                        add_warning(req, warning)
162
163                # FIXME: Deprecation notice for old namespace.
164                # Remove notice code when migration to new namespace is complete
6165                if 'http://bitten.cmlenz.net/tools/' in config.recipe:
0166                    add_notice(req, "Recipe uses a deprecated namespace. "
0167                        "Replace 'http://bitten.cmlenz.net/tools/' with "
0168                        "'http://bitten.edgewall.org/tools/'.")
169
170                # Add a notice if configuration is not active
6171                if not warnings and not config.active and config.recipe:
0172                    add_notice(req, "Configuration is not active. Activate "
0173                        "from main 'Configurations' listing to enable it.")
174
175                # Prepare template variables
6176                data['config'] = {
6177                    'name': config.name, 'label': config.label or config.name,
6178                    'active': config.active, 'path': config.path,
6179                    'min_rev': config.min_rev, 'max_rev': config.max_rev,
6180                    'description': config.description,
6181                    'recipe': config.recipe,
6182                    'platforms': [{
6183                        'name': platform.name,
6184                        'id': platform.id,
6185                        'href': req.href.admin('bitten', 'configs', config.name,
6186                                               platform.id),
6187                        'rules': [{'property': propname, 'pattern': pattern}
6188                                   for propname, pattern in platform.rules]
7189                    } for platform in platforms]
7190                }
191
0192        else: # At the top level build config list
15193            if req.method == 'POST':
13194                if 'add' in req.args: # Add build config
5195                    config = self._create_config(req)
1196                    req.redirect(req.abs_href.admin(cat, page, config.name))
197
8198                elif 'remove' in req.args: # Remove selected build configs
4199                    self._remove_configs(req)
200
4201                elif 'apply' in req.args: # Update active state of configs
2202                    self._activate_configs(req)
5203                req.redirect(req.abs_href.admin(cat, page))
204
205            # Prepare template variables
2206            configs = []
4207            for config in BuildConfig.select(self.env, include_inactive=True):
2208                configs.append({
2209                    'name': config.name, 'label': config.label or config.name,
2210                    'active': config.active, 'path': config.path,
2211                    'min_rev': config.min_rev, 'max_rev': config.max_rev,
2212                    'href': req.href.admin('bitten', 'configs', config.name),
2213                    'recipe': config.recipe and True or False
2214                })
4215            data['configs'] = sorted(configs, key=lambda x:x['label'].lower())
216
9217        add_stylesheet(req, 'bitten/admin.css')
9218        add_script(req, 'common/js/suggest.js')
9219        return 'bitten_admin_configs.html', data
220
221    # Internal methods
222
1223    def _activate_configs(self, req):
2224        req.perm.assert_permission('BUILD_MODIFY')
225
2226        active = req.args.get('active') or []
2227        active = isinstance(active, list) and active or [active]
228
2229        db = self.env.get_db_cnx()
2230        for config in list(BuildConfig.select(self.env, db=db,
6231                                              include_inactive=True)):
4232            config.active = config.name in active
4233            config.update(db=db)
2234        db.commit()
235
1236    def _create_config(self, req):
5237        req.perm.assert_permission('BUILD_CREATE')
238
4239        config = BuildConfig(self.env)
4240        warnings = self._update_config(req, config)
4241        if warnings:
3242            if len(warnings) == 1:
3243                raise TracError(warnings[0], 'Add Configuration')
3244            else:
0245                raise TracError('Errors: %s' % ' '.join(warnings),
0246                                'Add Configuration')
1247        return config
248
1249    def _remove_configs(self, req):
4250        req.perm.assert_permission('BUILD_DELETE')
251
3252        sel = req.args.get('sel')
3253        if not sel:
1254            raise TracError('No configuration selected')
2255        sel = isinstance(sel, list) and sel or [sel]
256
2257        db = self.env.get_db_cnx()
3258        for name in sel:
2259            config = BuildConfig.fetch(self.env, name, db=db)
2260            if not config:
1261                raise TracError('Configuration %r not found' % name)
1262            config.delete(db=db)
1263        db.commit()
264
1265    def _update_config(self, req, config):
10266        warnings = []
10267        req.perm.assert_permission('BUILD_MODIFY')
268
10269        name = req.args.get('name')
10270        if not name:
2271            warnings.append('Missing required field "name".')
10272        if name and not re.match(r'^[\w.-]+$', name):
2273            warnings.append('The field "name" may only contain letters, '
2274                            'digits, periods, or dashes.')
275
10276        repos = self.env.get_repository(authname=req.authname)
10277        if not repos:
0278            warnings.append('No "(default)" Repository: Add a repository or '
0279                            'alias named "(default)" to Trac.')
10280        path = req.args.get('path', '')
10281        min_rev = req.args.get('min_rev') or None
10282        max_rev = req.args.get('max_rev') or None
283
10284        if repos:
10285            path = repos.normalize_path(path)
10286            try:
10287                node = repos.get_node(path, max_rev)
8288                assert node.isdir, '%s is not a directory' % node.path
2289            except (AssertionError, TracError), e:
2290                warnings.append('Invalid Repository Path: "%s" does not exist '
2291                                'within the "(default)" repository.' % path)
10292            if min_rev:
0293                try:
0294                    repos.get_node(path, min_rev)
0295                except TracError, e:
0296                    warnings.append('Invalid Oldest Revision: %s.' % unicode(e))
297
10298        recipe_xml = req.args.get('recipe', '')
10299        if recipe_xml:
2300            try:
2301                Recipe(xmlio.parse(recipe_xml)).validate()
2302            except xmlio.ParseError, e:
1303                warnings.append('Failure parsing recipe: %s.' % unicode(e))
1304            except InvalidRecipeError, e:
1305                warnings.append('Invalid Recipe: %s.' % unicode(e))
306
10307        config.name = name
10308        config.path = path
10309        config.recipe = recipe_xml
10310        config.min_rev = min_rev
10311        config.max_rev = max_rev
10312        config.label = req.args.get('label', config.name)
10313        config.description = req.args.get('description', '')
314
10315        if warnings: # abort
8316            return warnings
317
2318        if config.exists:
1319            config.update()
1320        else:
1321            config.insert()
2322        return []
323
1324    def _create_platform(self, req, config_name):
2325        req.perm.assert_permission('BUILD_MODIFY')
326
2327        name = req.args.get('platform_name')
2328        if not name:
1329            raise TracError('Missing required field "name"', 'Missing field')
330
1331        platform = TargetPlatform(self.env, config=config_name, name=name)
1332        platform.insert()
1333        return platform
334
1335    def _remove_platforms(self, req):
2336        req.perm.assert_permission('BUILD_MODIFY')
337
2338        sel = req.args.get('sel')
2339        if not sel:
1340            raise TracError('No platform selected')
1341        sel = isinstance(sel, list) and sel or [sel]
342
1343        db = self.env.get_db_cnx()
2344        for platform_id in sel:
1345            platform = TargetPlatform.fetch(self.env, platform_id, db=db)
1346            if not platform:
0347                raise TracError('Target platform %r not found' % platform_id)
1348            platform.delete(db=db)
1349        db.commit()
350
1351    def _update_platform(self, req, platform):
1352        platform.name = req.args.get('name')
353
1354        properties = [int(key[9:]) for key in req.args.keys()
5355                      if key.startswith('property_')]
1356        properties.sort()
1357        patterns = [int(key[8:]) for key in req.args.keys()
5358                    if key.startswith('pattern_')]
1359        patterns.sort()
1360        platform.rules = [(req.args.get('property_%d' % property).strip(),
1361                           req.args.get('pattern_%d' % pattern).strip())
1362                          for property, pattern in zip(properties, patterns)
1363                          if req.args.get('property_%d' % property)]
364
1365        if platform.exists:
1366            platform.update()
1367        else:
0368            platform.insert()
369
1370        add_rules = [int(key[9:]) for key in req.args.keys()
5371                     if key.startswith('add_rule_')]
1372        if add_rules:
0373            platform.rules.insert(add_rules[0] + 1, ('', ''))
0374            return False
1375        rm_rules = [int(key[8:]) for key in req.args.keys()
5376                    if key.startswith('rm_rule_')]
1377        if rm_rules:
0378            if rm_rules[0] < len(platform.rules):
0379                del platform.rules[rm_rules[0]]
0380            return False
381
1382        return True
Note: See TracBrowser for help on using the repository browser.