Example #1
0
 def test_validate_successful(self):
     xml = xmlio.parse('<build>'
                       '<step id="foo"><somecmd></somecmd></step>'
                       '<step id="bar"><othercmd></othercmd></step>'
                       '</build>')
     recipe = Recipe(xml, basedir=self.basedir)
     recipe.validate()
Example #2
0
 def test_validate_successful(self):
     xml = xmlio.parse('<build>'
                       '<step id="foo"><somecmd></somecmd></step>'
                       '<step id="bar"><othercmd></othercmd></step>'
                       '</build>')
     recipe = Recipe(xml, basedir=self.basedir)
     recipe.validate()
Example #3
0
    def _process_build_initiation(self, req, config, build):
        self.log.info('Build slave %r initiated build %d', build.slave,
                      build.id)
        build.started = int(time.time())
        build.last_activity = build.started
        build.update()

        for listener in BuildSystem(self.env).listeners:
            listener.build_started(build)

        repos_name, repos, repos_path = get_repos(self.env, config.path,
                                                  req.authname)
        xml = xmlio.parse(config.recipe)
        xml.attr['path'] = config.path
        xml.attr['revision'] = build.rev
        xml.attr['config'] = config.name
        xml.attr['build'] = str(build.id)
        target_platform = TargetPlatform.fetch(self.env, build.platform)
        xml.attr['platform'] = target_platform.name
        xml.attr['name'] = build.slave
        xml.attr['form_token'] = req.form_token  # For posting attachments
        xml.attr['reponame'] = repos_name != '(default)' and repos_name or ''
        xml.attr['repopath'] = repos_path.strip('/')
        body = str(xml)

        self.log.info('Build slave %r initiated build %d', build.slave,
                      build.id)

        # create the first step, mark it as in-progress.

        recipe = Recipe(xmlio.parse(config.recipe))
        stepname = recipe.__iter__().next().id

        step = self._start_new_step(build, stepname)
        step.insert()

        self._send_response(req,
                            200,
                            body,
                            headers={
                                'Content-Type':
                                'application/x-bitten+xml',
                                'Content-Length':
                                str(len(body)),
                                'Content-Disposition':
                                'attachment; filename=recipe_%s_r%s.xml' %
                                (config.name, build.rev)
                            })
Example #4
0
    def _execute_build(self, build_url, fileobj):
        build_id = build_url and int(build_url.split('/')[-1]) or 0
        xml = xmlio.parse(fileobj)
        basedir = ''
        try:
            recipe = Recipe(xml, os.path.join(self.work_dir, self.build_dir), 
                            self.config)
            basedir = recipe.ctxt.basedir
            log.debug('Running build in directory %s' % basedir)
            if not os.path.exists(basedir):
                os.mkdir(basedir)

            for step in recipe:
                log.info('Executing build step %r', step.id)
                if not self._execute_step(build_url, recipe, step):
                    log.warning('Stopping build due to failure')
                    break
            else:
                log.info('Build completed')
            if self.dry_run:
                self._cancel_build(build_url)
        finally:
            if not self.keep_files and os.path.isdir(basedir):
                log.debug('Removing build directory %s' % basedir)
                _rmtree(basedir)
            if self.single_build:
                log.info('Exiting after single build completed.')
                raise ExitSlave(EX_OK)
Example #5
0
    def _execute_build(self, build_url, fileobj):
        build_id = build_url and int(build_url.split('/')[-1]) or 0
        xml = xmlio.parse(fileobj)
        basedir = ''
        try:
            if not self.local:
                keepalive_thread = KeepAliveThread(self.opener, build_url,
                                                   self.single_build,
                                                   self.keepalive_interval)
                keepalive_thread.start()
            recipe = Recipe(xml, os.path.join(self.work_dir, self.build_dir),
                            self.config)
            basedir = recipe.ctxt.basedir
            log.debug('Running build in directory %s' % basedir)
            if not os.path.exists(basedir):
                os.mkdir(basedir)

            for step in recipe:
                try:
                    log.info('Executing build step %r, onerror = %s', step.id,
                             step.onerror)
                    if not self._execute_step(build_url, recipe, step):
                        log.warning('Stopping build due to failure')
                        break
                except Exception, e:
                    log.error(
                        'Exception raised processing step %s. Reraising %s',
                        step.id, e)
                    raise
            else:
Example #6
0
 def test_validate_step_with_duplicate_id(self):
     xml = xmlio.parse('<build>'
                       '<step id="test"><somecmd></somecmd></step>'
                       '<step id="test"><othercmd></othercmd></step>'
                       '</build>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)
Example #7
0
 def test_empty_step(self):
     xml = xmlio.parse('<build>'
                       ' <step id="foo" description="Bar"></step>'
                       '</build>')
     recipe = Recipe(xml, basedir=self.basedir)
     steps = list(recipe)
     self.assertEqual(1, len(steps))
     self.assertEqual('foo', steps[0].id)
     self.assertEqual('Bar', steps[0].description)
     self.assertEqual('fail', steps[0].onerror)
Example #8
0
 def test_onerror_override(self):
     xml = xmlio.parse('<build onerror="ignore">'
                       ' <step id="foo" description="Bar" onerror="continue"></step>'
                       '</build>')
     recipe = Recipe(xml, basedir=self.basedir)
     steps = list(recipe)
     self.assertEqual(1, len(steps))
     self.assertEqual('foo', steps[0].id)
     self.assertEqual('Bar', steps[0].description)
     self.assertEqual('continue', steps[0].onerror)
Example #9
0
    def _process_build_initiation(self, req, config, build):
        self.log.info('Build slave %r initiated build %d', build.slave,
                      build.id)
        build.started = int(time.time())
        build.last_activity = build.started
        build.update()

        for listener in BuildSystem(self.env).listeners:
            listener.build_started(build)

        xml = xmlio.parse(config.recipe)
        xml.attr['path'] = config.path
        xml.attr['revision'] = build.rev
        xml.attr['config'] = config.name
        xml.attr['build'] = str(build.id)
        target_platform = TargetPlatform.fetch(self.env, build.platform)
        xml.attr['platform'] = target_platform.name
        xml.attr['name'] = build.slave
        xml.attr['form_token'] = req.form_token # For posting attachments
        body = str(xml)

        self.log.info('Build slave %r initiated build %d', build.slave,
                      build.id)

        # create the first step, mark it as in-progress.

        recipe = Recipe(xmlio.parse(config.recipe))
        stepname = recipe.__iter__().next().id

        step = self._start_new_step(build, stepname)
        step.insert()

        self._send_response(req, 200, body, headers={
                    'Content-Type': 'application/x-bitten+xml',
                    'Content-Length': str(len(body)),
                    'Content-Disposition':
                        'attachment; filename=recipe_%s_r%s.xml' %
                        (config.name, build.rev)})
Example #10
0
class BuildConfigurationsAdminPageProvider(Component):
    """Web administration panel for configuring the build master."""

    implements(IAdminPanelProvider)

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if req.perm.has_permission('BUILD_MODIFY'):
            yield ('bitten', 'Builds', 'configs', 'Configurations')

    def render_admin_panel(self, req, cat, page, path_info):
        data = {}

        # Analyze url
        try:
            config_name, platform_id = path_info.split('/', 1)
        except:
            config_name = path_info
            platform_id = None

        if config_name:  # Existing build config
            warnings = []
            if platform_id or (
                    # Editing or creating one of the config's target platforms
                    req.method == 'POST' and 'new' in req.args):

                if platform_id:  # Editing target platform
                    platform_id = int(platform_id)
                    platform = TargetPlatform.fetch(self.env, platform_id)

                    if req.method == 'POST':
                        if 'cancel' in req.args or \
                                self._update_platform(req, platform):
                            req.redirect(
                                req.abs_href.admin(cat, page, config_name))
                else:  # creating target platform
                    platform = self._create_platform(req, config_name)
                    req.redirect(
                        req.abs_href.admin(cat, page, config_name,
                                           platform.id))

                # Set up template variables
                data['platform'] = {
                    'id':
                    platform.id,
                    'name':
                    platform.name,
                    'exists':
                    platform.exists,
                    'rules': [{
                        'property': propname,
                        'pattern': pattern
                    } for propname, pattern in platform.rules] or [('', '')]
                }

            else:  # Editing existing build config itself
                config = BuildConfig.fetch(self.env, config_name)
                platforms = list(
                    TargetPlatform.select(self.env, config=config.name))

                if req.method == 'POST':
                    if 'remove' in req.args:  # Remove selected platforms
                        self._remove_platforms(req)
                        add_notice(req, "Target Platform(s) Removed.")
                        req.redirect(req.abs_href.admin(
                            cat, page, config.name))

                    elif 'save' in req.args:  # Save this build config
                        warnings = self._update_config(req, config)

                    if not warnings:
                        add_notice(req, "Configuration Saved.")
                        req.redirect(req.abs_href.admin(
                            cat, page, config.name))

                    for warning in warnings:
                        add_warning(req, warning)

                # FIXME: Deprecation notice for old namespace.
                # Remove notice code when migration to new namespace is complete
                if 'http://bitten.cmlenz.net/tools/' in config.recipe:
                    add_notice(
                        req, "Recipe uses a deprecated namespace. "
                        "Replace 'http://bitten.cmlenz.net/tools/' with "
                        "'http://bitten.edgewall.org/tools/'.")

                # Add a notice if configuration is not active
                if not warnings and not config.active and config.recipe:
                    add_notice(
                        req, "Configuration is not active. Activate "
                        "from main 'Configurations' listing to enable it.")

                # Prepare template variables
                data['config'] = {
                    'name':
                    config.name,
                    'label':
                    config.label or config.name,
                    'active':
                    config.active,
                    'branch':
                    config.branch,
                    'description':
                    config.description,
                    'recipe':
                    config.recipe,
                    'platforms': [{
                        'name':
                        platform.name,
                        'id':
                        platform.id,
                        'href':
                        req.href.admin('bitten', 'configs', config.name,
                                       platform.id),
                        'rules': [{
                            'property': propname,
                            'pattern': pattern
                        } for propname, pattern in platform.rules]
                    } for platform in platforms]
                }

        else:  # At the top level build config list
            if req.method == 'POST':
                if 'add' in req.args:  # Add build config
                    config = self._create_config(req)
                    req.redirect(req.abs_href.admin(cat, page, config.name))

                elif 'remove' in req.args:  # Remove selected build configs
                    self._remove_configs(req)

                elif 'apply' in req.args:  # Update active state of configs
                    self._activate_configs(req)
                req.redirect(req.abs_href.admin(cat, page))

            # Prepare template variables
            configs = []
            for config in BuildConfig.select(self.env, include_inactive=True):
                configs.append({
                    'name':
                    config.name,
                    'label':
                    config.label or config.name,
                    'active':
                    config.active,
                    'branch':
                    config.branch,
                    'href':
                    req.href.admin('bitten', 'configs', config.name),
                    'recipe':
                    config.recipe and True or False
                })
            data['configs'] = configs

        add_stylesheet(req, 'bitten/admin.css')
        add_script(req, 'common/js/suggest.js')
        return 'bitten_admin_configs.html', data

    # Internal methods

    def _activate_configs(self, req):
        req.perm.assert_permission('BUILD_MODIFY')

        active = req.args.get('active') or []
        active = isinstance(active, list) and active or [active]

        db = self.env.get_db_cnx()
        for config in list(
                BuildConfig.select(self.env, db=db, include_inactive=True)):
            config.active = config.name in active
            config.update(db=db)
        db.commit()

    def _create_config(self, req):
        req.perm.assert_permission('BUILD_CREATE')

        config = BuildConfig(self.env)
        warnings = self._update_config(req, config)
        if warnings:
            if len(warnings) == 1:
                raise TracError(warnings[0], 'Add Configuration')
            else:
                raise TracError('Errors: %s' % ' '.join(warnings),
                                'Add Configuration')
        return config

    def _remove_configs(self, req):
        req.perm.assert_permission('BUILD_DELETE')

        sel = req.args.get('sel')
        if not sel:
            raise TracError('No configuration selected')
        sel = isinstance(sel, list) and sel or [sel]

        db = self.env.get_db_cnx()
        for name in sel:
            config = BuildConfig.fetch(self.env, name, db=db)
            if not config:
                raise TracError('Configuration %r not found' % name)
            config.delete(db=db)
        db.commit()

    def _update_config(self, req, config):
        warnings = []
        req.perm.assert_permission('BUILD_MODIFY')

        name = req.args.get('name')
        if not name:
            warnings.append('Missing required field "name".')
        if name and not re.match(r'^[\w.-]+$', name):
            warnings.append('The field "name" may only contain letters, '
                            'digits, periods, or dashes.')

        branch = req.args.get('branch', '')
        repos = self.env.get_repository(req.authname)
        try:
            if not (branch in [b[0] for b in repos.git.get_branches()
                               ]) and branch != 'master':
                raise TracError('Invalid Repository Branch "%s".' % branch)
        except (AssertionError, TracError), e:
            warnings.append('Invalid Repository Branch "%s".' % branch)

        recipe_xml = req.args.get('recipe', '')
        if recipe_xml:
            try:
                Recipe(xmlio.parse(recipe_xml)).validate()
            except xmlio.ParseError, e:
                warnings.append('Failure parsing recipe: %s.' % unicode(e))
            except InvalidRecipeError, e:
                warnings.append('Invalid Recipe: %s.' % unicode(e))
Example #11
0
 def test_validate_step_with_empty_id(self):
     xml = xmlio.parse('<build><step id=""><cmd/></step></build>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)
Example #12
0
            elem = xmlio.parse(req.read())
        except xmlio.ParseError, e:
            self.log.error('Error parsing build step result: %s',
                           e,
                           exc_info=True)
            self._send_error(req, HTTP_BAD_REQUEST, 'XML parser error')
        stepname = elem.attr['step']

        # we should have created this step previously; if it hasn't,
        # the master and slave are processing steps out of order.
        step = BuildStep.fetch(self.env, build=build.id, name=stepname)
        if not step:
            self._send_error(req, HTTP_CONFLICT,
                             'Build step has not been created.')

        recipe = Recipe(xmlio.parse(config.recipe))
        index = None
        current_step = None
        for num, recipe_step in enumerate(recipe):
            if recipe_step.id == stepname:
                index = num
                current_step = recipe_step
        if index is None:
            self._send_error(req, HTTP_FORBIDDEN,
                             'No such build step' % stepname)
        last_step = index == num

        self.log.debug(
            'Slave %s (build %d) completed step %d (%s) with '
            'status %s', build.slave, build.id, index, stepname,
            elem.attr['status'])
Example #13
0
        try:
            node = repos.get_node(repos_path, max_rev)
            assert node.isdir, '%s is not a directory' % node.path
        except (AssertionError, TracError), e:
            warnings.append('Invalid Repository Path "%s".' % path)

        if min_rev:
            try:
                repos.get_node(repos_path, min_rev)
            except TracError, e:
                warnings.append('Invalid Oldest Revision: %s.' % unicode(e))

        recipe_xml = req.args.get('recipe', '')
        if recipe_xml:
            try:
                Recipe(xmlio.parse(recipe_xml)).validate()
            except xmlio.ParseError, e:
                warnings.append('Failure parsing recipe: %s.' % unicode(e))
            except InvalidRecipeError, e:
                warnings.append('Invalid Recipe: %s.' % unicode(e))

        config.name = name
        config.path = __multirepos__ and path or repos.normalize_path(path)
        config.recipe = recipe_xml
        config.min_rev = min_rev
        config.max_rev = max_rev
        config.label = req.args.get('label', config.name)
        config.description = req.args.get('description', '')

        if warnings:  # abort
            return warnings
Example #14
0
 def test_validate_bad_root(self):
     xml = xmlio.parse('<foo></foo>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)
Example #15
0
 def test_validate_child_not_step(self):
     xml = xmlio.parse('<build><foo/></build>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)
Example #16
0
 def test_empty_recipe(self):
     xml = xmlio.parse('<build/>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertEqual(self.basedir, recipe.ctxt.basedir)
     steps = list(recipe)
     self.assertEqual(0, len(steps))
Example #17
0
 def test_validate_step_without_commands(self):
     xml = xmlio.parse('<build><step id="test"/></build>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)
Example #18
0
 def test_validate_step_with_command_children(self):
     xml = xmlio.parse('<build><step id="test">'
                       '<somecmd><child1/><child2/></somecmd>'
                       '</step></build>')
     recipe = Recipe(xml, basedir=self.basedir)
     self.assertRaises(InvalidRecipeError, recipe.validate)