Example #1
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        # ReviewBot options.
        self.request_default_return = True
        self.comment_handler = True

        self.do_comments = True

        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = None
        self.review_team_group = None
        self.legal_review_group = None
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}
Example #2
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False
Example #3
0
 def __init__(self, *args, **kwargs):
     ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
     self.maintbot = MaintenanceChecker(*args, **kwargs)
     # for FactorySourceChecker
     self.lookup_checker = FactorySourceChecker(*args, **kwargs)
     self.lookup_checker.parse_lookup('openSUSE:Leap:42.2')
     self.lookup_checker.parse_lookup('openSUSE:Leap:42.2:NonFree')
     self.factory = FactorySourceChecker(*args, **kwargs)
     # XXX: FactorySourceChecker should be able to handle that itself
     self.factory_nonfree = FactorySourceChecker(*args, **kwargs)
     self.factory_nonfree.factory = 'openSUSE:Factory:NonFree'
Example #4
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        # ReviewBot options.
        self.request_default_return = True
        self.comment_handler = True

        self.do_comments = True

        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = None
        self.review_team_group = None
        self.legal_review_group = None
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}
Example #5
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False
Example #6
0
    def setUp(self):
        """
        Initialize the configuration
        """
        super().setUp()

        Cache.last_updated[APIURL] = {'__oldest': '2016-12-18T11:49:37Z'}
        httpretty.reset()
        httpretty.enable(allow_net_connect=False)

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl=APIURL,
                                            user='******',
                                            logger=self.logger)
        self.checker.override_allow = False  # Test setup cannot handle.
Example #7
0
    def setUp(self):
        """
        Initialize the configuration
        """

        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, 'oscrc')
        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        #osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl = APIURL, \
                user = '******', \
                logger = self.logger)
Example #8
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        self.do_comments = True

        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = 'leap-reviewers'
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}
    def setUp(self):
        """
        Initialize the configuration
        """

        Cache.last_updated[APIURL] = {'__oldest': '2016-12-18T11:49:37Z'}
        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, 'oscrc')
        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        #osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl = APIURL, \
                user = '******', \
                logger = self.logger)
        self.checker.override_allow = False # Test setup cannot handle.
Example #10
0
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        self.do_comments = True
        self.commentapi = CommentAPI(self.apiurl)

        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = 'leap-reviewers'
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False

        self.comment_marker_re = re.compile(r'<!-- leaper state=(?P<state>done|seen)(?: result=(?P<result>accepted|declined))? -->')

        self.comment_log = None
        self.commentlogger = LogToString(self, 'comment_log')
        self.logger.addFilter(self.commentlogger)
    def setUp(self):
        """
        Initialize the configuration
        """

        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, "oscrc")
        osc.core.conf.get_config(override_conffile=oscrc, override_no_keyring=True, override_no_gnome_keyring=True)
        # osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl=APIURL, user="******", logger=self.logger)
    def setUp(self):
        """
        Initialize the configuration
        """

        Cache.last_updated[APIURL] = {'__oldest': '2016-12-18T11:49:37Z'}
        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, 'oscrc')
        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        #osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl = APIURL, \
                user = '******', \
                logger = self.logger)
        self.checker.override_allow = False # Test setup cannot handle.
Example #13
0
class Leaper(ReviewBot.ReviewBot):

    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        self.do_comments = True
        self.commentapi = CommentAPI(self.apiurl)

        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = 'leap-reviewers'
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False

        self.comment_marker_re = re.compile(r'<!-- leaper state=(?P<state>done|seen)(?: result=(?P<result>accepted|declined))? -->')

        self.comment_log = None
        self.commentlogger = LogToString(self, 'comment_log')
        self.logger.addFilter(self.commentlogger)

    def prepare_review(self):

        # update lookup information on every run
        self.factory.parse_lookup('openSUSE:Leap:42.2')
        self.factory.parse_lookup('openSUSE:Leap:42.2:NonFree')
        self.lookup_422 = self.factory.lookup.copy()
        self.factory.lookup = {}
        self.factory.parse_lookup('openSUSE:Leap:42.1:Update')
        self.lookup_421 = self.factory.lookup.copy()
        self.factory.lookup = {}

    def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
        self.logger.info("%s/%s@%s -> %s/%s"%(src_project, src_package, src_rev, target_project, target_package))
        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" % (src_project, src_package, src_rev))
            return False

        origin = None
        if package in self.lookup_422:
            origin = self.lookup_422[package]

        is_fine_if_factory = False
        not_in_factory_okish = False
        if origin:
            self.logger.info("expected origin is '%s'", origin)
            if origin.startswith('Devel;'):
                (dummy, origin, dummy) = origin.split(';')
                if origin != src_project:
                    self.logger.debug("not submitted from devel project")
                    return False
                is_fine_if_factory = True
                not_in_factory_okish = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Factory'):
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                if origin == src_project:
                    self.source_in_factory = True
                    return True
                is_fine_if_factory = True
                # fall through to check history and requests
            elif origin == 'FORK':
                is_fine_if_factory = True
                not_in_factory_okish = True
                self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Leap:42.1'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                # submitted from :Update
                if src_project.startswith(origin):
                    self.logger.debug("submission from 42.1 ok")
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self.factory._check_project('openSUSE:Leap:42.1:Update', target_package, src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 42.1")
                        return good
                    # check release requests too
                    good = self.factory._check_requests('openSUSE:Leap:42.1:Update', target_package, src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                if package in self.lookup_421:
                    oldorigin = self.lookup_421[package]
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    if oldorigin.startswith('openSUSE:Factory'):
                        self.logger.info("Package was from Factory in 42.1")
                        # check if an attempt to switch to SLE package is made
                        good = self.factory._check_project('SUSE:SLE-12-SP2:GA', target_package, src_srcinfo.verifymd5)
                        if good:
                            self.logger.info("request sources come from SLE")
                            self.needs_release_manager = True
                            return good
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
                self.needs_release_manager = True

            elif origin.startswith('SUSE:SLE-12'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                # submitted from :Update
                if origin == src_project:
                    self.logger.debug("submission origin ok")
                    return True
                elif origin.endswith(':GA') \
                    and src_project == origin[:-2]+'Update':
                    self.logger.debug("sle update submission")
                    return True
                # submitted from higher SP
                if origin.startswith('SUSE:SLE-12:'):
                    if src_project.startswith('SUSE:SLE-12-SP1:') \
                        or src_project.startswith('SUSE:SLE-12-SP2:'):
                            self.logger.info("submission from service pack ok")
                            return True
                elif origin.startswith('SUSE:SLE-12-SP1:'):
                    if src_project.startswith('SUSE:SLE-12-SP2:'):
                        self.logger.info("submission from service pack ok")
                        return True

                self.needs_release_manager = True
                good = self._check_project_and_request('openSUSE:Leap:42.2:SLE-workarounds', target_package, src_srcinfo)
                if good or good == None:
                    self.logger.info("found sources in SLE-workarounds")
                    return good
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
            else:
                self.logger.error("unhandled origin %s", origin)
                return False
        else: # no origin
            # submission from SLE is ok
            if src_project.startswith('SUSE:SLE-12'):
                return True

            is_fine_if_factory = True
            self.needs_release_manager = True

        # we came here because none of the above checks find it good, so
        # let's see if the package is in Factory at least
        is_in_factory = self._check_factory(target_package, src_srcinfo)
        if is_in_factory:
            self.source_in_factory = True
            self.needs_reviewteam = False
        elif is_in_factory is None:
            self.pending_factory_submission = True
            self.needs_reviewteam = False
        else:
            if src_project.startswith('SUSE:SLE-12') \
                or src_project.startswith('openSUSE:Leap:42.'):
                self.needs_reviewteam = False
            else:
                self.needs_reviewteam = True
            self.source_in_factory = False

        if is_fine_if_factory:
            if self.source_in_factory:
                return True
            elif self.pending_factory_submission:
                return None
            elif not_in_factory_okish:
                self.needs_reviewteam = True
                return True

        return False

    def _check_factory(self, target_package, src_srcinfo):
            good = self.factory._check_project('openSUSE:Factory', target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests('openSUSE:Factory', target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to Factory")
                return good
            good = self.factory._check_project('openSUSE:Factory:NonFree', target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests('openSUSE:Factory:NonFree', target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to Factory:NonFree")
                return good
            return False

    def _check_project_and_request(self, project, target_package, src_srcinfo):
        good = self.factory._check_project(project, target_package, src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests(project, target_package, src_srcinfo.verifymd5)
        if good or good == None:
            return good
        return False

    def check_one_request(self, req):
        self.review_messages = self.DEFAULT_REVIEW_MESSAGES.copy()
        self.needs_reviewteam = False
        self.needs_release_manager = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.comment_log = []

        if len(req.actions) != 1:
            msg = "only one action per request please"
            self.review_messages['declined'] = msg
            return False

        request_ok = ReviewBot.ReviewBot.check_one_request(self, req)
        has_correct_maintainer = self.maintbot.check_one_request(req)

        self.logger.debug("review result: %s", request_ok)
        self.logger.debug("has_correct_maintainer: %s", has_correct_maintainer)
        if self.pending_factory_submission:
            self.logger.info("submission is waiting for a Factory request to complete")
        elif self.source_in_factory:
            self.logger.info("the submitted sources are in or accepted for Factory")
        elif self.source_in_factory == False:
            self.logger.info("the submitted sources are NOT in Factory")

        if request_ok == False:
            self.logger.info("NOTE: if you think the automated review was wrong here, please talk to the release team before reopening the request")
        elif self.needs_release_manager:
            self.logger.info("request needs review by release management")

        if self.comment_log:
            result = None
            if request_ok is None:
                state = 'seen'
            elif request_ok:
                state = 'done'
                result = 'accepted'
            else:
                state = 'done'
                result = 'declined'
            self.add_comment(req, '\n\n'.join(self.comment_log), state)
        self.comment_log = None

        if self.needs_release_manager:
            add_review = True
            for r in req.reviews:
                if r.by_group == self.release_manager_group and (r.state == 'new' or r.state == 'accepted'):
                    add_review = False
                    self.logger.debug("%s already is a reviewer", self.release_manager_group)
                    break
            if add_review:
                if self.add_review(req, by_group = self.release_manager_group) != True:
                    self.review_messages['declined'] += '\nadding %s failed' % self.release_manager_group
                    return False

        if self.needs_reviewteam:
            add_review = True
            self.logger.info("%s needs review by opensuse-review-team"%req.reqid)
            for r in req.reviews:
                if r.by_group == 'opensuse-review-team':
                    add_review = False
                    self.logger.debug("opensuse-review-team already is a reviewer")
                    break
            if add_review:
                if self.add_review(req, by_group = "opensuse-review-team") != True:
                    self.review_messages['declined'] += '\nadding opensuse-review-team failed'
                    return False

        return request_ok

    def check_action__default(self, req, a):
        # decline all other requests for fallback reviewer
        self.logger.debug("auto decline request type %s"%a.type)
        return False

    # TODO: make generic, move to Reviewbot. Used by multiple bots
    def add_comment(self, req, msg, state, result=None):
        if not self.do_comments:
            return

        comment = "<!-- leaper state=%s%s -->\n" % (state, ' result=%s' % result if result else '')
        comment += "\n" + msg

        (comment_id, comment_state, comment_result, comment_text) = self.find_obs_request_comment(req, state)

        if comment_id is not None and state == comment_state:
            # count number of lines as aproximation to avoid spamming requests
            # for slight wording changes in the code
            if len(comment_text.split('\n')) == len(comment.split('\n')):
                self.logger.debug("not worth the update, previous comment %s is state %s", comment_id, comment_state)
                return

        self.logger.debug("adding comment to %s, state %s result %s", req.reqid, state, result)
        self.logger.debug("message: %s", msg)
        if not self.dryrun:
            if comment_id is not None:
                self.commentapi.delete(comment_id)
            self.commentapi.add_comment(request_id=req.reqid, comment=str(comment))

    def find_obs_request_comment(self, req, state=None):
        """Return previous comments (should be one)."""
        if self.do_comments:
            comments = self.commentapi.get_comments(request_id=req.reqid)
            for c in comments.values():
                m = self.comment_marker_re.match(c['comment'])
                if m and (state is None or state == m.group('state')):
                    return c['id'], m.group('state'), m.group('result'), c['comment']
        return None, None, None, None

    def check_action__default(self, req, a):
        self.logger.info("unhandled request type %s"%a.type)
        self.needs_release_manager = True
        return True
class TestFactorySourceAccept(unittest.TestCase):

    def setUp(self):
        """
        Initialize the configuration
        """

        Cache.last_updated[APIURL] = {'__oldest': '2016-12-18T11:49:37Z'}
        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, 'oscrc')
        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        #osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl = APIURL, \
                user = '******', \
                logger = self.logger)
        self.checker.override_allow = False # Test setup cannot handle.

    def test_accept_request(self):

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/00Meta/lookup.yml"),
            status = 404)

        httpretty.register_uri(httpretty.GET,
            APIURL + "/request/770001",
            body = """
                <request id="770001" creator="chameleon">
                  <action type="submit">
                    <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                    <target project="openSUSE:13.2" package="timezone"/>
                  </action>
                  <state name="new" who="factory-source" when="2014-10-08T12:06:07">
                    <comment>...</comment>
                  </state>
                  <review state="new" by_user="******"/>
                  <description>...</description>
                </request>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/Base:System/timezone?rev=481ecbe0dfc63ece3a1f1b5598f7d96c&view=info"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="481ecbe0dfc63ece3a1f1b5598f7d96c"
                    srcmd5="481ecbe0dfc63ece3a1f1b5598f7d96c"
                    verifymd5="67bac34d29d70553239d33aaf92d2fdd">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)
        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/timezone?view=info"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="89"
                    vrev="1"
                    srcmd5="a36605617cbeefa8168bf0ccf3058074"
                    verifymd5="a36605617cbeefa8168bf0ccf3058074">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/timezone/_history?limit=5"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="89"
                    vrev="1"
                    srcmd5="a36605617cbeefa8168bf0ccf3058074"
                    verifymd5="a36605617cbeefa8168bf0ccf3058074">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)
        httpretty.register_uri(httpretty.GET,
            rr("/search/request?match=%28state%2F%40name%3D%27new%27+or+state%2F%40name%3D%27review%27%29+and+%28action%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+action%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27%29+and+%28action%2Ftarget%2F%40package%3D%27timezone%27+or+action%2Fsource%2F%40package%3D%27timezone%27%29+and+action%2F%40type%3D%27submit%27"),
            match_querystring = True,
            responses = [
                httpretty.Response( body = """
                    <collection matches="1">
                      <request id="254684" creator="chameleon">
                        <action type="submit">
                          <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                          <target project="openSUSE:Factory" package="timezone"/>
                        </action>
                        <state name="review" who="factory-auto" when="2014-10-08T11:55:56">
                          <comment>...</comment>
                        </state>
                        <review state="new" by_group="opensuse-review-team">
                          <comment/>
                        </review>
                        <description> ... </description>
                      </request>
                    </collection>
                    """),
                httpretty.Response( body = """
                    <collection matches="1">
                      <request id="254684" creator="chameleon">
                        <action type="submit">
                          <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                          <target project="openSUSE:Factory" package="timezone"/>
                        </action>
                        <state name="new" who="factory-auto" when="2014-10-08T11:55:56">
                          <comment>...</comment>
                        </state>
                        <description> ... </description>
                      </request>
                    </collection>
                    """)
                ])

        result = { 'status' : None }

        def change_request(result, method, uri, headers):
            u = urlparse(uri)
            if u.query == 'newstate=accepted&cmd=changereviewstate&by_user=factory-source':
                result['status'] = True
            else:
                result['status'] = 'ERROR'
            return (200, headers, '<status code="blah"/>')

        httpretty.register_uri(httpretty.POST,
            APIURL + "/request/770001",
            body = lambda method, uri, headers: change_request(result, method, uri, headers))

        # first time request is in in review
        self.checker.set_request_ids(['770001'])
        self.checker.check_requests()

        self.assertEqual(result['status'], None)

        # second time request is in state new so we can accept
        self.checker.set_request_ids(['770001'])
        self.checker.check_requests()

        self.assertTrue(result['status'])

    def test_source_not_in_factory(self):

        httpretty.register_uri(httpretty.GET,
            rr("/search/request?withfullhistory=1&match=state%2F%40name%3D%27review%27+and+review%5B%40by_user%3D%27factory-source%27+and+%40state%3D%27new%27%5D"),
            match_querystring = True,
            body = """
                <collection matches="1">
                    <request id="261411" creator="lnussel">
                      <action type="maintenance_incident">
                        <source project="home:lnussel:branches:openSUSE:Backports:SLE-12" package="plan" rev="71e76daf2c2e9ddb0b9208f54a14f608"/>
                        <target project="openSUSE:Maintenance" releaseproject="openSUSE:Backports:SLE-12"/>
                      </action>
                      <state name="review" who="maintbot" when="2014-11-13T13:22:02">
                        <comment></comment>
                      </state>
                      <review state="accepted" when="2014-11-13T13:22:02" who="maintbot" by_user="******">
                        <comment>accepted</comment>
                        <history who="maintbot" when="2014-11-13T16:43:09">
                          <description>Review got accepted</description>
                          <comment>accepted</comment>
                        </history>
                      </review>
                      <review state="new" by_user="******"/>
                      <history who="lnussel" when="2014-11-13T13:22:02">
                        <description>Request created</description>
                        <comment>test update</comment>
                      </history>
                      <history who="maintbot" when="2014-11-13T16:43:08">
                        <description>Request got a new review request</description>
                      </history>
                      <description>test update</description>
                    </request>
                </collection>
            """)

        httpretty.register_uri(httpretty.GET,
            APIURL + "/request/261411",
            body = """
                <request id="261411" creator="lnussel">
                  <action type="maintenance_incident">
                    <source project="home:lnussel:branches:openSUSE:Backports:SLE-12" package="plan" rev="71e76daf2c2e9ddb0b9208f54a14f608"/>
                    <target project="openSUSE:Maintenance" releaseproject="openSUSE:Backports:SLE-12"/>
                  </action>
                  <state name="review" who="maintbot" when="2014-11-13T13:22:02">
                    <comment></comment>
                  </state>
                  <review state="accepted" when="2014-11-13T13:22:02" who="maintbot" by_user="******">
                    <comment>accepted</comment>
                    <history who="maintbot" when="2014-11-13T16:43:09">
                      <description>Review got accepted</description>
                      <comment>accepted</comment>
                    </history>
                  </review>
                  <review state="new" by_user="******"/>
                  <history who="lnussel" when="2014-11-13T13:22:02">
                    <description>Request created</description>
                    <comment>test update</comment>
                  </history>
                  <history who="maintbot" when="2014-11-13T16:43:08">
                    <description>Request got a new review request</description>
                  </history>
                  <description>test update</description>
                </request>
            """)

        httpretty.register_uri(httpretty.GET,
            APIURL + "/source/home:lnussel:branches:openSUSE:Backports:SLE-12/plan",
            body = """
                <directory name="plan" rev="1" vrev="1" srcmd5="b4ed19dc30c1b328168bc62a81ec6998">
                  <linkinfo project="home:lnussel:plan" package="plan" srcmd5="7a2353f73b29dba970702053229542a0" baserev="7a2353f73b29dba970702053229542a0" xsrcmd5="71e76daf2c2e9ddb0b9208f54a14f608" lsrcmd5="b4ed19dc30c1b328168bc62a81ec6998" />
                  <entry name="_link" md5="91f81d88456818a18a7332999fb2da18" size="125" mtime="1415807350" />
                  <entry name="plan.spec" md5="b6814215f6d2e8559b43de9a214b2cbd" size="8103" mtime="1413627959" />
                </directory>

            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/plan?view=info"),
            match_querystring = True,
            status = 404,
            body = """
                <status code="unknown_package">
                    <summary>openSUSE:Factory/plan</summary>
                </status>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/00Meta/lookup.yml"),
            status = 404)

        httpretty.register_uri(httpretty.GET,
            rr("/search/request?match=%28state%2F%40name%3D%27new%27+or+state%2F%40name%3D%27review%27%29+and+%28action%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+action%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27%29+and+%28action%2Ftarget%2F%40package%3D%27plan%27+or+action%2Fsource%2F%40package%3D%27plan%27%29+and+action%2F%40type%3D%27submit%27"),
            match_querystring = True,
            body = """
                <collection matches="0">
                </collection>
            """)

        result = { 'factory_source_declined' : None }

        def change_request(result, method, uri, headers):
            u = urlparse(uri)
            if u.query == 'newstate=declined&cmd=changereviewstate&by_user=factory-source':
                result['factory_source_declined'] = True
            return (200, headers, '<status code="ok"/>')

        httpretty.register_uri(httpretty.POST,
            APIURL + "/request/261411",
            body = lambda method, uri, headers: change_request(result, method, uri, headers))

        self.checker.requests = []
        self.checker.set_request_ids_search_review()
        self.checker.check_requests()

        self.assertTrue(result['factory_source_declined'])
Example #15
0
class Leaper(ReviewBot.ReviewBot):

    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        # ReviewBot options.
        self.request_default_return = True
        self.comment_handler = True

        self.do_comments = True

        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = None
        self.review_team_group = None
        self.legal_review_group = None
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}

    def prepare_review(self):
        # update lookup information on every run

        self.lookup.reset()

    def get_source_packages(self, project, expand=False):
        """Return the list of packages in a project."""
        query = {'expand': 1} if expand else {}
        try:
            root = ET.parse(osc.core.http_GET(osc.core.makeurl(self.apiurl, ['source', project],
                                     query=query))).getroot()
            packages = [i.get('name') for i in root.findall('entry')]
        except HTTPError as e:
            # in case the project doesn't exist yet (like sle update)
            if e.code != 404:
                raise e
            packages = []

        return packages

    def is_package_in_project(self, project, package):
        if not project in self.packages:
            self.packages[project] = self.get_source_packages(project)
        return True if package in self.packages[project] else False

    # this is so bad
    def _webui_from_api(self, apiurl):
        return apiurl.replace('//api.', '//build.')

    # OBS is not smart enough to redirect to the mapped instance and just
    # displays 404 so we have to do it ourselves
    def package_link(self, project, package):
        apiurl = self.apiurl
        for prefix, url, srprefix in self.config.project_namespace_api_map:
            if project.startswith(prefix):
                apiurl = url
                project = project[len(prefix):]
                break

        return '[%(project)s/%(package)s](%(url)s/package/show/%(project)s/%(package)s)'%{
                    'url': self._webui_from_api(apiurl),
                    'project': project,
                    'package': package,
                    }

    def rdiff_link(self, src_project, src_package, src_rev, target_project, target_package = None):
        if target_package is None:
            target_package = src_package

        apiurl = self.apiurl
        for prefix, url, srprefix in self.config.project_namespace_api_map:
            # can't rdiff if both sides are remote
            # https://github.com/openSUSE/open-build-service/issues/4601
            if target_project.startswith(prefix) and src_project.startswith(prefix):
                apiurl = url
                src_project = src_project[len(prefix):]
                target_project = target_project[len(prefix):]
                break

        return self.package_link(target_project, target_package) + ' ([diff](%(url)s/package/rdiff/%(src_project)s/%(src_package)s?opackage=%(target_package)s&oproject=%(target_project)s&rev=%(src_rev)s))'%{
                'url': self._webui_from_api(apiurl),
                'src_project': src_project,
                'src_package': src_package,
                'src_rev': src_rev,
                'target_project': target_project,
                'target_package': target_package,
                }

    def _check_same_origin(self, origin, project):

        if origin == 'FORK':
            return True

        if origin.startswith('Devel;'):
            (dummy, origin, dummy) = origin.split(';')

        # FIXME: to make the rest of the code easier this should probably check
        # if the srcmd5 matches the origin project. That way it doesn't really
        # matter from where something got submitted as long as the sources match.
        return project.startswith(origin)

    def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
        ret = self.check_source_submission_inner(src_project, src_package, src_rev, target_project, target_package)

        # The layout of this code is just plain wrong and awkward. What is
        # really desired a "post check source submission" not
        # check_one_request() which applies to all action types and all actions
        # at once. The follow-ups need the same context that the main method
        # determining to flip the switch had. For maintenance incidents the
        # ReviewBot code does a fair bit of mangling to the package and project
        # values passed in which is not present during check_one_request().
        # Currently the only feature used by maintenance is
        # do_check_maintainer_review and the rest of the checks apply in the
        # single action workflow so this should work fine, but really all the
        # processing should be done per-action instead of per-request.

        if self.do_check_maintainer_review:
            self.devel_project_review_ensure(self.request, target_project, target_package)

        return ret

    def check_source_submission_inner(self, src_project, src_package, src_rev, target_project, target_package):
        super(Leaper, self).check_source_submission(src_project, src_package, src_rev, target_project, target_package)
        self.automatic_submission = False

        if src_project == target_project and src_package == target_package:
            self.logger.info('self submission detected')
            self.needs_release_manager = True
            return True

        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        origin = self.lookup.get(target_project, package)
        origin_same = True
        if origin:
            origin_same = self._check_same_origin(origin, src_project)

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" % (src_project, src_package, src_rev))
            return False

        if self.ibs and target_project.startswith('SUSE:SLE'):

            review_result = None
            prj = 'openSUSE.org:openSUSE:Factory'
            # True or None (open request) are acceptable for SLE.
            in_factory = self._check_factory(package, src_srcinfo, prj)
            if in_factory:
                review_result = True
                self.source_in_factory = True
            elif in_factory is None:
                self.pending_factory_submission = True
            else:
                if not self.is_package_in_project(prj, package):
                    self.logger.info('the package is not in Factory, nor submitted there')
                else:
                    self.logger.info('different sources in {}'.format(self.rdiff_link(src_project, src_package, src_rev, prj, package)))

            if review_result == None and not self.pending_factory_submission:
                other_projects_to_check = []
                m = re.match(r'SUSE:SLE-(\d+)(?:-SP(\d+)):', target_project)
                if m:
                    sle_version = int(m.group(1))
                    sp_version = int(m.group(2))
                    versions_to_check = []
                    # yeah, too much harcoding here
                    if sle_version == 12:
                        versions_to_check = [ '42.3' ]
                    elif sle_version == 15:
                        versions_to_check = [ '15.%d'%i for i in range(sp_version+1) ]
                    else:
                        self.logger.error("can't handle %d.%d", sle_version, sp_version)

                    for version in versions_to_check:
                        leap = 'openSUSE.org:openSUSE:Leap:%s'%(version)
                        other_projects_to_check += [ leap, leap + ':Update', leap + ':NonFree', leap + ':NonFree:Update' ]

                for prj in other_projects_to_check:
                    if self.is_package_in_project(prj, package):
                        self.logger.debug('checking {}'.format(prj))
                        if self._check_factory(package, src_srcinfo, prj) is True:
                            self.logger.info('found source match in {}'.format(prj))

                devel_project, devel_package = devel_project_get(self.apiurl, 'openSUSE.org:openSUSE:Factory', package)
                if devel_project is not None:
                    # specifying devel package is optional
                    if devel_package is None:
                        devel_package = package
                    if self.is_package_in_project(devel_project, devel_package):
                        if self._check_matching_srcmd5(devel_project, devel_package, src_srcinfo.verifymd5) == True:
                            self.logger.info('matching sources in {}/{}'.format(devel_project, devel_package))
                            return True
                        else:
                            self.logger.info('different sources in devel project {}'.format(self.rdiff_link(src_project, src_package, src_rev, devel_project, devel_package)))
                else:
                    self.logger.info('no devel project found for {}/{}'.format('openSUSE.org:openSUSE:Factory', package))

                self.logger.info('no matching sources found anywhere. Needs a human to decide whether that is ok. Please provide some justification to help that person.')
            else:
                leap = 'openSUSE.org:openSUSE:Leap:15.1'
                if not self.is_package_in_project(target_project, package) \
                    and self.is_package_in_project(leap, package) \
                    and self._check_factory(package, src_srcinfo, leap) is False:
                    self.logger.info('different sources in {}'.format(self.rdiff_link(src_project, src_package, src_rev, leap, package)))

            if not review_result and origin is not None:
                review_result = origin_same
                if origin_same or origin == 'openSUSE.org:openSUSE:Factory' and self.pending_factory_submission:
                    self.logger.info("ok, origin %s unchanged", origin)
                else:
                    # only log origin state if it's taken into consideration for the review result
                    self.logger.info("Submitted from a different origin than expected ('%s')", origin)
                    self.needs_release_manager = True
                    # no result so far and also no factory submission to wait
                    # for. So just pass to avoid requring too much overrides
                    if not self.pending_factory_submission:
                        review_result = True

            if not review_result and self.override_allow:
                # Rather than decline, leave review open in-case of change and
                # ask release manager for input via override comment.
                self.logger.info('Comment `(at){} override accept` to force accept.'.format(self.review_user))
                self.needs_release_manager = True
                review_result = None

            return review_result

        if target_project.endswith(':Update'):
            self.logger.info("expected origin is '%s' (%s)", origin,
                             "unchanged" if origin_same else "changed")

            # Only when not from current product should request require maintainer review.
            self.do_check_maintainer_review = False

            if origin_same:
                return True

            good = self._check_matching_srcmd5(origin, target_package, src_srcinfo.verifymd5)
            if good:
                self.logger.info('submission source found in origin ({})'.format(origin))
                return good
            good = self.factory._check_requests(origin, target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.info('found pending submission against origin ({})'.format(origin))
                return good

            # TODO #1662: Uncomment once maintbot has been superseded and leaper
            # is no longer run in comment-only mode.
            #self.do_check_maintainer_review = True

            return None
        elif self.action.type == 'maintenance_incident':
            self.logger.debug('unhandled incident pattern (targetting non :Update project)')
            return True

        # obviously
        if src_project in ('openSUSE:Factory', 'openSUSE:Factory:NonFree'):
            self.source_in_factory = True

        is_fine_if_factory = False
        not_in_factory_okish = False
        if origin:
            self.logger.info("expected origin is '%s' (%s)", origin,
                    "unchanged" if origin_same else "changed")
            if origin.startswith('Devel;'):
                if origin_same == False:
                    self.logger.debug("not submitted from devel project")
                    return False
                is_fine_if_factory = True
                not_in_factory_okish = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Factory'):
                # A large number of requests are created by hand that leaper
                # would have created via update_crawler.py. This applies to
                # other origins, but primary looking to let Factory submitters
                # know that there is no need to make manual submissions to both.
                # Since it has a lookup entry it is not a new package.
                self.automatic_submission = False
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                if origin == src_project:
                    self.source_in_factory = True
                    # no need to approve submissions from Factory if
                    # the lookup file points to Factory. Just causes
                    # spam for many maintainers #1393
                    self.do_check_maintainer_review = False
                is_fine_if_factory = True
                # fall through to check history and requests
            elif origin == 'FORK':
                is_fine_if_factory = True
                if not src_project.startswith('SUSE:SLE-'):
                    not_in_factory_okish = True
                    self.needs_check_source = True
                self.needs_release_manager = True
                # fall through to check history and requests
            # TODO Ugly save for 15.1 (n-1).
            elif origin.startswith('openSUSE:Leap:15.0'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                if src_project.startswith('openSUSE:Leap'):
                    self.do_check_maintainer_review = False
                # submitted from :Update
                if origin_same:
                    self.logger.debug("submission from 15.0 ok")
                    return True
                # switching to sle package might make sense
                if src_project.startswith('SUSE:SLE-15'):
                    self.needs_release_manager = True
                    self.do_check_maintainer_review = False
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self._check_matching_srcmd5('openSUSE:Leap:15.0:Update', target_package, src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 15.0")
                        return good
                    # check release requests too
                    good = self.factory._check_requests('openSUSE:Leap:15.0:Update', target_package, src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                oldorigin = self.lookup.get('openSUSE:Leap:15.0', target_package)
                if oldorigin:
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    self.logger.info("Package was from %s in 15.0", oldorigin)
                    if oldorigin.startswith('openSUSE:Factory'):
                        # check if an attempt to switch to SLE package is made
                        for sp in ('SP1:GA', 'SP1:Update'):
                            good = self._check_matching_srcmd5('SUSE:SLE-15-{}'.format(sp), target_package, src_srcinfo.verifymd5)
                            if good:
                                self.logger.info("request sources come from SLE")
                                self.needs_release_manager = True
                                return good
                    # TODO Ugly save for 15.2 (n-2).
                    elif False and oldorigin.startswith('openSUSE:Leap:15.0'):
                        self.logger.info("Package was from %s in 15.0", oldorigin)
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
                self.needs_release_manager = True

            elif origin.startswith('SUSE:SLE-15'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                if src_project.startswith('SUSE:SLE-15'):
                    self.do_check_maintainer_review = False
                for v in ('15.0', '15.1'):
                    prj = 'openSUSE:Leap:{}:SLE-workarounds'.format(v)
                    if self.is_package_in_project( prj, target_package):
                        self.logger.info("found package in %s", prj)
                        if not self._check_matching_srcmd5(prj,
                                target_package,
                                src_srcinfo.verifymd5):
                            self.logger.info("sources in %s are NOT identical",
                                    self.rdiff_link(src_project, src_package, src_rev, prj, package))

                        self.needs_release_manager = True
                # submitted from :Update
                if origin == src_project:
                    self.logger.debug("submission origin ok")
                    return True
                elif origin.endswith(':GA') \
                    and src_project == origin[:-2]+'Update':
                    self.logger.debug("sle update submission")
                    return True

                # check  if submitted from higher SP
                priolist = ['SUSE:SLE-15:', 'SUSE:SLE-15-SP1:', 'SUSE:SLE-15-SP2:', 'SUSE:SLE-15-SP3:']
                for i in range(len(priolist)-1):
                    if origin.startswith(priolist[i]):
                        for prj in priolist[i+1:]:
                            if src_project.startswith(prj):
                                self.logger.info("submission from higher service pack %s:* ok", prj)
                                return True

                in_sle_origin = self._check_factory(target_package, src_srcinfo, origin)
                if in_sle_origin:
                    self.logger.info('parallel submission, also in {}'.format(origin))
                    return True

                self.needs_release_manager = True
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
            else:
                self.logger.error("unhandled origin %s", origin)
                return False
        else: # no origin
            # submission from SLE is ok
            if src_project.startswith('SUSE:SLE-15'):
                self.do_check_maintainer_review = False
                return True

            # new package submitted from Factory. Check if it was in
            # 42.3 before and skip maintainer review if so.
            subprj = src_project[len('openSUSE:Factory'):]
            # disabled for reference. Needed again for 16.0 probably
            if False and self.source_in_factory and target_project.startswith('openSUSE:Leap:15.0') \
                and self.is_package_in_project('openSUSE:Leap:42.3'+subprj, package):
                self.logger.info('package was in 42.3')
                self.do_check_maintainer_review = False
                return True

            is_fine_if_factory = True
            self.needs_release_manager = True

        if origin is None or not origin.startswith('SUSE:SLE-'):
            for p in (':Update', ':GA'):
                prj = 'SUSE:SLE-15' + p
                if self.is_package_in_project(prj, package):
                    self.logger.info('Package is in {}'.format(
                        self.rdiff_link(src_project, src_package, src_rev, prj, package)))
                    break

        is_in_factory = self.source_in_factory

        # we came here because none of the above checks find it good, so
        # let's see if the package is in Factory at least
        if is_in_factory is None:
            is_in_factory = self._check_factory(package, src_srcinfo)
        if is_in_factory:
            self.source_in_factory = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        elif is_in_factory is None:
            self.pending_factory_submission = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        else:
            if src_project.startswith('SUSE:SLE-15') \
                or src_project.startswith('openSUSE:Leap:15.'):
                self.needs_reviewteam = False
                self.needs_legal_review = False
            else:
                self.needs_reviewteam = True
                self.needs_legal_review = True
            self.source_in_factory = False

        if is_fine_if_factory:
            if self.source_in_factory:
                return True
            elif self.pending_factory_submission:
                return None
            elif not_in_factory_okish:
                self.needs_reviewteam = True
                self.needs_legal_review = True
                return True

        if self.override_allow:
            # Rather than decline, leave review open and ask release
            # manager for input via override comment.
            self.logger.info('Comment `(at){} override accept` to force accept.'.format(self.review_user))
            self.needs_release_manager = True
            return None

        return False

    def _check_factory(self, target_package, src_srcinfo, target_project='openSUSE:Factory'):
        for subprj in ('', ':NonFree', ':Live'):
            prj = ''.join((target_project, subprj))
            good = self._check_matching_srcmd5(prj, target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests(prj, target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to %s", prj)
                return good

        return False

    def _check_project_and_request(self, project, target_package, src_srcinfo):
        good = self._check_matching_srcmd5(project, target_package, src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests(project, target_package, src_srcinfo.verifymd5)
        if good or good == None:
            return good
        return False

    def check_one_request(self, req):
        config = Config.get(self.apiurl, req.actions[0].tgt_project)
        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.needs_release_manager = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.do_check_maintainer_review = not self.ibs
        self.packages = {}

        request_ok = ReviewBot.ReviewBot.check_one_request(self, req)

        self.logger.debug("review result: %s", request_ok)
        if self.pending_factory_submission:
            self.logger.info("submission is waiting for a Factory request to complete")
            creator = req.get_creator()
            bot_name = self.bot_name.lower()
            if self.automatic_submission and creator != bot_name:
                self.logger.info('@{}: this request would have been automatically created by {} after the Factory submission was accepted in order to eleviate the need to manually create requests for packages sourced from Factory'.format(creator, bot_name))
        elif self.source_in_factory:
            self.logger.info("perfect. the submitted sources are in or accepted for Factory")
        elif self.source_in_factory == False:
            self.logger.warn("the submitted sources are NOT in Factory")

        if request_ok == False:
            self.logger.info("NOTE: if you think the automated review was wrong here, please talk to the release team before reopening the request")

        if self.do_comments:
            result = None
            if request_ok is None:
                state = 'seen'
            elif request_ok:
                state = 'done'
                result = 'accepted'
            else:
                state = 'done'
                result = 'declined'

            self.comment_write(state, result)

        add_review_groups = []
        if self.needs_release_manager:
            add_review_groups.append(self.release_manager_group or
                                     config.get(self.override_group_key))
        if self.needs_reviewteam:
            add_review_groups.append(self.review_team_group or
                                     config.get('review-team'))
        if self.needs_legal_review:
            add_review_groups.append(self.legal_review_group or
                                     config.get('legal-review-group'))
        if self.needs_check_source and self.check_source_group is not None:
            add_review_groups.append(self.check_source_group)

        for group in add_review_groups:
            if group is None:
                continue
            self.logger.info("{0} needs review by [{1}](/group/show/{1})".format(req.reqid, group))
            self.add_review(req, by_group=group)

        return request_ok

    def check_action__default(self, req, a):
        self.needs_release_manager = True
        return super(Leaper, self).check_action__default(req, a)
Example #16
0
class Leaper(ReviewBot.ReviewBot):

    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False

    def prepare_review(self):

        # update lookup information on every run
        self.factory.parse_lookup('openSUSE:Leap:42.2')
        self.factory.parse_lookup('openSUSE:Leap:42.2:NonFree')
        self.lookup_422 = self.factory.lookup.copy()
        self.factory.lookup = {}
        self.factory.parse_lookup('openSUSE:Leap:42.1:Update')
        self.lookup_421 = self.factory.lookup.copy()
        self.factory.lookup = {}

    def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
        self.logger.info("%s/%s@%s -> %s/%s"%(src_project, src_package, src_rev, target_project, target_package))
        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" % (src_project, src_package, src_rev))
            return False

        origin = None
        if package in self.lookup_422:
            origin = self.lookup_422[package]

        if origin:
            self.logger.debug("origin {}".format(origin))
            if origin.startswith('Devel;'):
                self.needs_reviewteam = True
                (dummy, origin, dummy) = origin.split(';')
            if origin == src_project:
                self.logger.debug("exact match")
                return True
            elif origin.startswith('openSUSE:Factory'):
                return self._check_factory(target_package, src_srcinfo)
            elif origin.startswith('openSUSE:Leap:42.1'):
                # submitted from :Update
                if src_project.startswith(origin):
                    self.logger.debug("match 42.1")
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self.factory._check_project('openSUSE:Leap:42.1:Update', target_package, src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 42.1")
                        return good
                    # check release requests too
                    good = self.factory._check_requests('openSUSE:Leap:42.1:Update', target_package, src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                if package in self.lookup_421:
                    oldorigin = self.lookup_421[package]
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    if oldorigin.startswith('openSUSE:Factory'):
                        if src_project == oldorigin:
                            self.logger.debug("Upgrade to Factory again. Submitted from Factory")
                            return True
                        good = self._check_factory(target_package, src_srcinfo)
                        if good or good == None:
                            self.logger.debug("Upgrade to Factory again. It's in Factory")
                            return good
                        # or maybe in SP2?
                        good = self.factory._check_project('SUSE:SLE-12-SP2:GA', target_package, src_srcinfo.verifymd5)
                        if good:
                            self.logger.debug("hope it's ok to change to SP2")
                            return good
                # else other project or FORK, fall through

            elif origin.startswith('SUSE:SLE-12'):
                # submitted from :Update
                if src_project.startswith(origin):
                    self.logger.debug("match sle")
                    return True
                # submitted from higher SP
                if origin.startswith('SUSE:SLE-12'):
                    if src_project.startswith('SUSE:SLE-12-SP1') \
                        or src_project.startswith('SUSE:SLE-12-SP2'):
                            self.logger.debug("higher service pack ok")
                            return True
            # else other project or FORK, fall through

            # we came here because none of the above checks find it good, so
            # let's see if the package is in Factory at least
            is_in_factory = self._check_factory(target_package, src_srcinfo)
            if is_in_factory:
                self.source_in_factory = True
            elif is_in_factory is None:
                self.pending_factory_submission = True
            else:
                if not src_project.startswith('SUSE:SLE-12'):
                    self.needs_reviewteam = True

        else: # no origin
            # SLE and Factory are ok
            if src_project.startswith('SUSE:SLE-12') \
                or src_project.startswith('openSUSE:Factory'):
                return True
            # submitted from elsewhere, check it's in Factory
            good = self._check_factory(target_package, src_srcinfo)
            if good:
                self.source_in_factory = True
                return True
            elif good == None:
                self.pending_factory_submission = True
                return good
            # or maybe in SP2?
            good = self.factory._check_project('SUSE:SLE-12-SP2:GA', target_package, src_srcinfo.verifymd5)
            if good:
                return good

        return False

    def _check_factory(self, target_package, src_srcinfo):
            good = self.factory._check_project('openSUSE:Factory', target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests('openSUSE:Factory', target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to Factory")
                return good
            good = self.factory._check_project('openSUSE:Factory:NonFree', target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests('openSUSE:Factory:NonFree', target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to Factory:NonFree")
                return good
            return False

    def check_one_request(self, req):
        self.review_messages = self.DEFAULT_REVIEW_MESSAGES.copy()
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False

        if len(req.actions) != 1:
            msg = "only one action per request please"
            self.review_messages['declined'] = msg
            return False

        # if the fallback reviewer created the request she probably
        # knows what she does :-)
        if self.fallback_user and req.get_creator() == self.fallback_user:
            self.logger.debug("skip fallback review")
            return True

        has_upstream_sources = ReviewBot.ReviewBot.check_one_request(self, req)
        has_correct_maintainer = self.maintbot.check_one_request(req)

        # not reviewed yet?
        if has_upstream_sources is None:
            return None

        self.logger.debug("upstream sources: {}, maintainer ok: {}".format(has_upstream_sources, has_correct_maintainer))

        if self.needs_reviewteam:
            add_review = True
            self.logger.debug("%s needs review by opensuse-review-team"%req.reqid)
            for r in req.reviews:
                if r.by_group == 'opensuse-review-team':
                    add_review = False
                    self.logger.debug("opensuse-review-team already is a reviewer")
                    break
            if add_review:
                if self.add_review(req, by_group = "opensuse-review-team") != True:
                    self.review_messages['declined'] += '\nadding opensuse-review-team failed'
                    return False

        if has_upstream_sources != True or has_correct_maintainer != True:
            if has_upstream_sources != True:
                self.review_messages['declined'] += '\nOrigin project changed'
                pkg = req.actions[0].tgt_package
                if pkg in self.lookup_422:
                    self.review_messages['declined'] += '(was {})'.format(self.lookup_422[pkg])
                if self.source_in_factory:
                    self.review_messages['declined'] += '\nsource is in Factory'
                if self.pending_factory_submission:
                    self.review_messages['declined'] += '\na submission to Factory is pending'
                    self.logger.debug("origin changed but waiting for Factory submission to complete")
                    # FXIME: we should add the human reviewer here
                    # and leave a comment
                    return None
            # shouldn't happen actually
            if has_correct_maintainer != True:
                self.review_messages['declined'] += '\nMaintainer check failed'
            return False

        return True

    def check_action__default(self, req, a):
        # decline all other requests for fallback reviewer
        self.logger.debug("auto decline request type %s"%a.type)
        return False
Example #17
0
class Leaper(ReviewBot.ReviewBot):

    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        self.do_comments = True

        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = 'leap-reviewers'
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}

    def prepare_review(self):
        # update lookup information on every run

        if self.ibs:
            self.factory.parse_lookup('SUSE:SLE-12-SP3:GA')
            self.lookup_sp3 = self.factory.lookup.copy()
            return

        self.factory.parse_lookup('openSUSE:Leap:42.3')
        self.factory.parse_lookup('openSUSE:Leap:42.3:NonFree')
        self.lookup_423 = self.factory.lookup.copy()
        self.factory.reset_lookup()
        self.factory.parse_lookup('openSUSE:Leap:42.2:Update')
        self.factory.parse_lookup('openSUSE:Leap:42.2:NonFree:Update')
        self.lookup_422 = self.factory.lookup.copy()
        self.factory.reset_lookup()
        self.factory.parse_lookup('openSUSE:Leap:42.1:Update')
        self.lookup_421 = self.factory.lookup.copy()
        self.factory.reset_lookup()

    def get_source_packages(self, project, expand=False):
        """Return the list of packages in a project."""
        query = {'expand': 1} if expand else {}
        root = ET.parse(osc.core.http_GET(osc.core.makeurl(self.apiurl,['source', project],
                                 query=query))).getroot()
        packages = [i.get('name') for i in root.findall('entry')]

        return packages

    def is_package_in_project(self, project, package):
        if not project in self.packages:
            self.packages[project] = self.get_source_packages(project)
        return True if package in self.packages[project] else False

    def rdiff_link(self, src_project, src_package, src_rev, target_project, target_package = None):
        if target_package is None:
            target_package = src_package

        return '[%(target_project)s/%(target_package)s](/package/rdiff/%(src_project)s/%(src_package)s?opackage=%(target_package)s&oproject=%(target_project)s&rev=%(src_rev)s)'%{
                'src_project': src_project,
                'src_package': src_package,
                'src_rev': src_rev,
                'target_project': target_project,
                'target_package': target_package,
                }

    def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
        super(Leaper, self).check_source_submission(src_project, src_package, src_rev, target_project, target_package)
        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        origin = None

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" % (src_project, src_package, src_rev))
            return False

        if self.ibs and target_project.startswith('SUSE:SLE'):
            if package in self.lookup_sp3:
                origin = self.lookup_sp3[package]

            origin_same = True
            if origin:
                origin_same = True if origin == 'FORK' else src_project.startswith(origin)
                self.logger.info("expected origin is '%s' (%s)", origin,
                                 "unchanged" if origin_same else "changed")

            prj = 'openSUSE.org:openSUSE:Factory'
            # True or None (open request) are acceptable for SLE.
            self.source_in_factory = self._check_factory(package, src_srcinfo, prj)
            if self.source_in_factory is None:
                self.pending_factory_submission = True
            if self.source_in_factory is not False:
                return self.source_in_factory

            # got false. could mean package doesn't exist or no match
            if self.is_package_in_project(prj, package):
                self.logger.info('different sources in {}'.format(self.rdiff_link(src_project, src_package, src_rev, prj, package)))

            prj = 'openSUSE.org:openSUSE:Leap:42.2'
            if self.is_package_in_project(prj, package):
                if self._check_factory(package, src_srcinfo, prj) is True:
                    self.logger.info('found source match in {}'.format(prj))
                else:
                    self.logger.info('different sources in {}'.format(self.rdiff_link(src_project, src_package, src_rev, prj, package)))

            devel_project, devel_package = self.get_devel_project('openSUSE.org:openSUSE:Factory', package)
            if devel_project is not None:
                # specifying devel package is optional
                if devel_package is None:
                    devel_package = package
                if self.is_package_in_project(devel_project, devel_package):
                    if self.factory._check_project(devel_project, devel_package, src_srcinfo.verifymd5) == True:
                        self.logger.info('matching sources in {}/{}'.format(devel_project, devel_package))
                        return True
                    else:
                        self.logger.info('different sources in {}'.format(self.rdiff_link(src_project, src_package, src_rev, devel_project, devel_package)))
            else:
                self.logger.info('no devel project found for {}/{}'.format('openSUSE.org:openSUSE:Factory', package))

            self.logger.info('no matching sources in Factory, Leap:42.2, nor devel project')

            return origin_same

        if package in self.lookup_423:
            origin = self.lookup_423[package]

        is_fine_if_factory = False
        not_in_factory_okish = False
        if origin:
            origin_same = src_project.startswith(origin)
            self.logger.info("expected origin is '%s' (%s)", origin,
                             "unchanged" if origin_same else "changed")
            if origin.startswith('Devel;'):
                (dummy, origin, dummy) = origin.split(';')
                if origin != src_project:
                    self.logger.debug("not submitted from devel project")
                    return False
                is_fine_if_factory = True
                not_in_factory_okish = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Factory'):
                # A large number of requests are created by hand that leaper
                # would have created via update_crawler.py. This applies to
                # other origins, but primary looking to let Factory submitters
                # know that there is no need to make manual submissions to both.
                # Since it has a lookup entry it is not a new package.
                self.automatic_submission = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                if origin == src_project:
                    self.source_in_factory = True
                    return True
                is_fine_if_factory = True
                # fall through to check history and requests
            elif origin == 'FORK':
                is_fine_if_factory = True
                not_in_factory_okish = True
                self.needs_release_manager = True
                self.needs_check_source = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Leap:42.2'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                # submitted from :Update
                if origin_same:
                    self.logger.debug("submission from 42.2 ok")
                    return True
                # switching to sle package might make sense
                if src_project.startswith('SUSE:SLE-12'):
                    self.needs_release_manager = True
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self.factory._check_project('openSUSE:Leap:42.2:Update', target_package, src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 42.2")
                        return good
                    # check release requests too
                    good = self.factory._check_requests('openSUSE:Leap:42.2:Update', target_package, src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                if package in self.lookup_422:
                    oldorigin = self.lookup_422[package]
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    if oldorigin.startswith('openSUSE:Factory'):
                        self.logger.info("Package was from Factory in 42.2")
                        # check if an attempt to switch to SLE package is made
                        for sp in ('SP2:GA', 'SP2:Update', 'SP3:GA'):
                            good = self.factory._check_project('SUSE:SLE-12-{}'.format(sp), target_package, src_srcinfo.verifymd5)
                            if good:
                                self.logger.info("request sources come from SLE")
                                self.needs_release_manager = True
                                return good
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
                self.needs_release_manager = True

            elif origin.startswith('SUSE:SLE-12'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                for v in ('42.3', '42.2'):
                    prj = 'openSUSE:Leap:{}:SLE-workarounds'.format(v)
                    if self.is_package_in_project( prj, target_package):
                        self.logger.info("found package in %s", prj)
                        if not self.factory._check_project(prj,
                                target_package,
                                src_srcinfo.verifymd5):
                            self.logger.info("sources in %s are NOT identical", prj)

                        self.needs_release_manager = True
                # submitted from :Update
                if origin == src_project:
                    self.logger.debug("submission origin ok")
                    return True
                elif origin.endswith(':GA') \
                    and src_project == origin[:-2]+'Update':
                    self.logger.debug("sle update submission")
                    return True

                # check  if submitted from higher SP
                priolist = ['SUSE:SLE-12:', 'SUSE:SLE-12-SP1:', 'SUSE:SLE-12-SP2:', 'SUSE:SLE-12-SP3:']
                for i in range(len(priolist)-1):
                    if origin.startswith(priolist[i]):
                        for prj in priolist[i+1:]:
                            if src_project.startswith(prj):
                                self.logger.info("submission from higher service pack %s:* ok", prj)
                                return True

                self.needs_release_manager = True
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
            else:
                self.logger.error("unhandled origin %s", origin)
                return False
        else: # no origin
            # submission from SLE is ok
            if src_project.startswith('SUSE:SLE-12'):
                return True

            is_fine_if_factory = True
            self.needs_release_manager = True

        # we came here because none of the above checks find it good, so
        # let's see if the package is in Factory at least
        is_in_factory = self._check_factory(target_package, src_srcinfo)
        if is_in_factory:
            self.source_in_factory = True
            self.needs_reviewteam = False
        elif is_in_factory is None:
            self.pending_factory_submission = True
            self.needs_reviewteam = False
        else:
            if src_project.startswith('SUSE:SLE-12') \
                or src_project.startswith('openSUSE:Leap:42.'):
                self.needs_reviewteam = False
            else:
                self.needs_reviewteam = True
            self.source_in_factory = False

        if is_fine_if_factory:
            if self.source_in_factory:
                return True
            elif self.pending_factory_submission:
                return None
            elif not_in_factory_okish:
                self.needs_reviewteam = True
                return True

        return False

    def _check_factory(self, target_package, src_srcinfo, target_project='openSUSE:Factory'):
            good = self.factory._check_project(target_project, target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests(target_project, target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to Factory")
                return good
            target_project_nonfree = '{}:NonFree'.format(target_project)
            good = self.factory._check_project(target_project_nonfree, target_package, src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests(target_project_nonfree, target_package, src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug('found request to {}'.format(target_project_nonfree))
                return good
            return False

    def _check_project_and_request(self, project, target_package, src_srcinfo):
        good = self.factory._check_project(project, target_package, src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests(project, target_package, src_srcinfo.verifymd5)
        if good or good == None:
            return good
        return False

    def check_one_request(self, req):
        self.review_messages = self.DEFAULT_REVIEW_MESSAGES.copy()
        self.needs_reviewteam = False
        self.needs_release_manager = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.comment_handler_add()
        self.packages = {}

        if len(req.actions) != 1:
            msg = "only one action per request please"
            self.review_messages['declined'] = msg
            return False

        request_ok = ReviewBot.ReviewBot.check_one_request(self, req)
        if not self.ibs:
            has_correct_maintainer = self.maintbot.check_one_request(req)
            self.logger.debug("has_correct_maintainer: %s", has_correct_maintainer)

        self.logger.debug("review result: %s", request_ok)
        if self.pending_factory_submission:
            self.logger.info("submission is waiting for a Factory request to complete")
            creator = req.get_creator()
            bot_name = self.bot_name.lower()
            if self.automatic_submission and creator != bot_name:
                self.logger.info('@{}: this request would have been automatically created by {} after the Factory submission was accepted in order to eleviate the need to manually create requests for packages sourced from Factory'.format(creator, bot_name))
        elif self.source_in_factory:
            self.logger.info("the submitted sources are in or accepted for Factory")
        elif self.source_in_factory == False:
            self.logger.info("the submitted sources are NOT in Factory")

        if request_ok == False:
            self.logger.info("NOTE: if you think the automated review was wrong here, please talk to the release team before reopening the request")
        elif self.needs_release_manager:
            self.logger.info("request needs review by release management")

        if self.do_comments:
            result = None
            if request_ok is None:
                state = 'seen'
            elif request_ok:
                state = 'done'
                result = 'accepted'
            else:
                state = 'done'
                result = 'declined'
            # Since leaper calls other bots (like maintbot) comments may
            # sometimes contain identical lines (like for unhandled requests).
            self.comment_handler_lines_deduplicate()
            self.comment_write(state, result)

        if self.needs_release_manager:
            add_review = True
            for r in req.reviews:
                if r.by_group == self.release_manager_group and (r.state == 'new' or r.state == 'accepted'):
                    add_review = False
                    self.logger.debug("%s already is a reviewer", self.release_manager_group)
                    break
            if add_review:
                if self.add_review(req, by_group = self.release_manager_group) != True:
                    self.review_messages['declined'] += '\nadding %s failed' % self.release_manager_group
                    return False

        if self.needs_reviewteam:
            add_review = True
            self.logger.info("%s needs review by opensuse-review-team"%req.reqid)
            for r in req.reviews:
                if r.by_group == 'opensuse-review-team':
                    add_review = False
                    self.logger.debug("opensuse-review-team already is a reviewer")
                    break
            if add_review:
                if self.add_review(req, by_group = "opensuse-review-team") != True:
                    self.review_messages['declined'] += '\nadding opensuse-review-team failed'
                    return False

        if self.needs_check_source and self.check_source_group is not None:
            add_review = True
            self.logger.info("%s needs review by %s" % (req.reqid, self.check_source_group))
            for r in req.reviews:
                if r.by_group == self.check_source_group:
                    add_review = False
                    self.logger.debug("%s already is a reviewer", self.check_source_group)
                    break
            if add_review:
                if self.add_review(req, by_group = self.check_source_group) != True:
                    self.review_messages['declined'] += '\nadding %s failed' % self.check_source_group
                    return False

        return request_ok

    def check_action__default(self, req, a):
        super(Leaper, self).check_action__default(req, a)
        self.needs_release_manager = True
        return True
Example #18
0
class Leaper(ReviewBot.ReviewBot):

    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.lookup_checker = FactorySourceChecker(*args, **kwargs)
        self.lookup_checker.parse_lookup('openSUSE:Leap:42.2')
        self.lookup_checker.parse_lookup('openSUSE:Leap:42.2:NonFree')
        self.factory = FactorySourceChecker(*args, **kwargs)
        # XXX: FactorySourceChecker should be able to handle that itself
        self.factory_nonfree = FactorySourceChecker(*args, **kwargs)
        self.factory_nonfree.factory = 'openSUSE:Factory:NonFree'

    def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
        return self.lookup_checker.check_source_submission(src_project, src_package, src_rev, target_project, target_package)

    def check_one_request(self, req):
        self.review_messages = self.DEFAULT_REVIEW_MESSAGES.copy()

        if len(req.actions) != 1:
            msg = "only one action per request please"
            self.review_messages['declined'] = msg
            return False

        # if the fallback reviewer created the request she probably
        # knows what she does :-)
        if self.fallback_user and req.get_creator() == self.fallback_user:
            self.logger.debug("skip fallback review")
            return True

        has_upstream_sources = ReviewBot.ReviewBot.check_one_request(self, req)
        has_correct_maintainer = self.maintbot.check_one_request(req)

        # not reviewed yet?
        if has_upstream_sources is None:
            return None

        self.logger.debug("upstream sources: {}, maintainer ok: {}".format(has_upstream_sources, has_correct_maintainer))

        if has_upstream_sources != True or has_correct_maintainer != True:
            if has_upstream_sources != True:
                self.review_messages['declined'] += '\nOrigin project changed'
                pkg = req.actions[0].tgt_package
                prj = self.lookup_checker._package_get_upstream_project(pkg)
                if prj:
                    self.review_messages['declined'] += '(was {})'.format(prj)
                r = self.factory.check_one_request(req)
                if r == True:
                    self.review_messages['declined'] += '\nsource is in Factory though'
                elif r == None:
                    self.logger.info("waiting for review")
                    return None
                else:
                    r = self.factory_nonfree.check_one_request(req)
                    if r == True:
                        self.review_messages['declined'] += '\nsource is in Factory:NonFree though'
                    elif r == None:
                        self.logger.info("waiting for review")
                        return None
            # shouldn't happen actually
            if has_correct_maintainer != True:
                self.review_messages['declined'] += '\nMaintainer check failed'
            return False

        return True
Example #19
0
class Leaper(ReviewBot.ReviewBot):
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        # ReviewBot options.
        self.request_default_return = True
        self.comment_handler = True

        self.do_comments = True

        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = None
        self.review_team_group = None
        self.legal_review_group = None
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}

    def prepare_review(self):
        # update lookup information on every run

        self.lookup.reset()

    def get_source_packages(self, project, expand=False):
        """Return the list of packages in a project."""
        query = {'expand': 1} if expand else {}
        try:
            root = ET.parse(
                osc.core.http_GET(
                    osc.core.makeurl(self.apiurl, ['source', project],
                                     query=query))).getroot()
            packages = [i.get('name') for i in root.findall('entry')]
        except urllib2.HTTPError as e:
            # in case the project doesn't exist yet (like sle update)
            if e.code != 404:
                raise e
            packages = []

        return packages

    def is_package_in_project(self, project, package):
        if not project in self.packages:
            self.packages[project] = self.get_source_packages(project)
        return True if package in self.packages[project] else False

    def rdiff_link(self,
                   src_project,
                   src_package,
                   src_rev,
                   target_project,
                   target_package=None):
        if target_package is None:
            target_package = src_package

        return '[%(target_project)s/%(target_package)s](/package/show/%(target_project)s/%(target_package)s) ([diff](/package/rdiff/%(src_project)s/%(src_package)s?opackage=%(target_package)s&oproject=%(target_project)s&rev=%(src_rev)s))' % {
            'src_project': src_project,
            'src_package': src_package,
            'src_rev': src_rev,
            'target_project': target_project,
            'target_package': target_package,
        }

    def _check_same_origin(self, origin, project):

        if origin == 'FORK':
            return True

        if origin.startswith('Devel;'):
            (dummy, origin, dummy) = origin.split(';')

        # FIXME: to make the rest of the code easier this should probably check
        # if the srcmd5 matches the origin project. That way it doesn't really
        # matter from where something got submitted as long as the sources match.
        return project.startswith(origin)

    def check_source_submission(self, src_project, src_package, src_rev,
                                target_project, target_package):
        ret = self.check_source_submission_inner(src_project, src_package,
                                                 src_rev, target_project,
                                                 target_package)

        # The layout of this code is just plain wrong and awkward. What is
        # really desired a "post check source submission" not
        # check_one_request() which applies to all action types and all actions
        # at once. The follow-ups need the same context that the main method
        # determining to flip the switch had. For maintenance incidents the
        # ReviewBot code does a fair bit of mangling to the package and project
        # values passed in which is not present during check_one_request().
        # Currently the only feature used by maintenance is
        # do_check_maintainer_review and the rest of the checks apply in the
        # single action workflow so this should work fine, but really all the
        # processing should be done per-action instead of per-request.

        if self.do_check_maintainer_review:
            self.devel_project_review_ensure(self.request, target_project,
                                             target_package)

        return ret

    def check_source_submission_inner(self, src_project, src_package, src_rev,
                                      target_project, target_package):
        super(Leaper,
              self).check_source_submission(src_project, src_package, src_rev,
                                            target_project, target_package)
        self.automatic_submission = False

        if src_project == target_project and src_package == target_package:
            self.logger.info('self submission detected')
            self.needs_release_manager = True
            return True

        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        origin = self.lookup.get(target_project, package)
        origin_same = True
        if origin:
            origin_same = self._check_same_origin(origin, src_project)

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" %
                             (src_project, src_package, src_rev))
            return False

        if self.ibs and target_project.startswith('SUSE:SLE'):

            review_result = None
            prj = 'openSUSE.org:openSUSE:Factory'
            if self.is_package_in_project(prj, package):
                # True or None (open request) are acceptable for SLE.
                in_factory = self._check_factory(package, src_srcinfo, prj)
                if in_factory:
                    review_result = True
                    self.source_in_factory = True
                elif in_factory is None:
                    self.pending_factory_submission = True
                else:
                    self.logger.info('different sources in {}'.format(
                        self.rdiff_link(src_project, src_package, src_rev, prj,
                                        package)))
            else:
                self.logger.info(
                    'the package is not in Factory, nor submitted there')

            if review_result == None:
                other_projects_to_check = []
                m = re.match('SUSE:SLE-(\d+)(?:-SP(\d+)):', target_project)
                if m:
                    sle_version = int(m.group(1))
                    sp_version = int(m.group(2))
                    versions_to_check = []
                    # yeah, too much harcoding here
                    if sle_version == 12:
                        versions_to_check = ['42.3']
                    elif sle_version == 15:
                        versions_to_check = [
                            '15.%d' % i for i in range(sp_version + 1)
                        ]
                    else:
                        self.logger.error("can't handle %d.%d", sle_version,
                                          sp_version)

                    for version in versions_to_check:
                        leap = 'openSUSE.org:openSUSE:Leap:%s' % (version)
                        other_projects_to_check += [
                            leap, leap + ':Update', leap + ':NonFree',
                            leap + ':NonFree:Update'
                        ]

                for prj in other_projects_to_check:
                    if self.is_package_in_project(prj, package):
                        self.logger.info('checking {}'.format(prj))
                        if self._check_factory(package, src_srcinfo,
                                               prj) is True:
                            self.logger.info(
                                'found source match in {}'.format(prj))
                        else:
                            self.logger.info('different sources in {}'.format(
                                self.rdiff_link(src_project, src_package,
                                                src_rev, prj, package)))

                devel_project, devel_package = devel_project_get(
                    self.apiurl, 'openSUSE.org:openSUSE:Factory', package)
                if devel_project is not None:
                    # specifying devel package is optional
                    if devel_package is None:
                        devel_package = package
                    if self.is_package_in_project(devel_project,
                                                  devel_package):
                        if self._check_matching_srcmd5(
                                devel_project, devel_package,
                                src_srcinfo.verifymd5) == True:
                            self.logger.info(
                                'matching sources in {}/{}'.format(
                                    devel_project, devel_package))
                            return True
                        else:
                            self.logger.info(
                                'different sources in devel project {}'.format(
                                    self.rdiff_link(src_project, src_package,
                                                    src_rev, devel_project,
                                                    devel_package)))
                else:
                    self.logger.info('no devel project found for {}/{}'.format(
                        'openSUSE.org:openSUSE:Factory', package))

                self.logger.info(
                    'no matching sources found anywhere. Needs a human to decide whether that is ok. Please provide some justification to help that person.'
                )

            if not review_result:
                review_result = origin_same
                if origin_same:
                    self.logger.info("ok, origin %s unchanged", origin)
                else:
                    # only log origin state if it's taken into consideration for the review result
                    self.logger.info(
                        "Submitted from a different origin than expected ('%s')",
                        origin)

            if not review_result and self.override_allow:
                # Rather than decline, leave review open in-case of change and
                # ask release manager for input via override comment.
                self.logger.info(
                    'Comment `(at){} override accept` to force accept.'.format(
                        self.review_user))
                self.needs_release_manager = True
                review_result = None

            return review_result

        if target_project.endswith(':Update'):
            self.logger.info("expected origin is '%s' (%s)", origin,
                             "unchanged" if origin_same else "changed")

            # Only when not from current product should request require maintainer review.
            self.do_check_maintainer_review = False

            if origin_same:
                return True

            good = self._check_matching_srcmd5(origin, target_package,
                                               src_srcinfo.verifymd5)
            if good:
                self.logger.info(
                    'submission source found in origin ({})'.format(origin))
                return good
            good = self.factory._check_requests(origin, target_package,
                                                src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.info(
                    'found pending submission against origin ({})'.format(
                        origin))
                return good

            # TODO #1662: Uncomment once maintbot has been superseded and leaper
            # is no longer run in comment-only mode.
            #self.do_check_maintainer_review = True

            return None
        elif self.action.type == 'maintenance_incident':
            self.logger.debug(
                'unhandled incident pattern (targetting non :Update project)')
            return True

        # obviously
        if src_project in ('openSUSE:Factory', 'openSUSE:Factory:NonFree'):
            self.source_in_factory = True

        is_fine_if_factory = False
        not_in_factory_okish = False
        if origin:
            self.logger.info("expected origin is '%s' (%s)", origin,
                             "unchanged" if origin_same else "changed")
            if origin.startswith('Devel;'):
                if origin_same == False:
                    self.logger.debug("not submitted from devel project")
                    return False
                is_fine_if_factory = True
                not_in_factory_okish = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Factory'):
                # A large number of requests are created by hand that leaper
                # would have created via update_crawler.py. This applies to
                # other origins, but primary looking to let Factory submitters
                # know that there is no need to make manual submissions to both.
                # Since it has a lookup entry it is not a new package.
                self.automatic_submission = False
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                if origin == src_project:
                    self.source_in_factory = True
                    # no need to approve submissions from Factory if
                    # the lookup file points to Factory. Just causes
                    # spam for many maintainers #1393
                    self.do_check_maintainer_review = False
                is_fine_if_factory = True
                # fall through to check history and requests
            elif origin == 'FORK':
                is_fine_if_factory = True
                if not src_project.startswith('SUSE:SLE-'):
                    not_in_factory_okish = True
                    self.needs_check_source = True
                self.needs_release_manager = True
                # fall through to check history and requests
            # TODO Ugly save for 15.1 (n-1).
            elif origin.startswith('openSUSE:Leap:15.0'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                if src_project.startswith('openSUSE:Leap'):
                    self.do_check_maintainer_review = False
                # submitted from :Update
                if origin_same:
                    self.logger.debug("submission from 15.0 ok")
                    return True
                # switching to sle package might make sense
                if src_project.startswith('SUSE:SLE-15'):
                    self.needs_release_manager = True
                    self.do_check_maintainer_review = False
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self._check_matching_srcmd5(
                        'openSUSE:Leap:15.0:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 15.0")
                        return good
                    # check release requests too
                    good = self.factory._check_requests(
                        'openSUSE:Leap:15.0:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                oldorigin = self.lookup.get('openSUSE:Leap:15.0',
                                            target_package)
                if oldorigin:
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    self.logger.info("Package was from %s in 15.0", oldorigin)
                    if oldorigin.startswith('openSUSE:Factory'):
                        # check if an attempt to switch to SLE package is made
                        for sp in ('SP1:GA', 'SP1:Update'):
                            good = self._check_matching_srcmd5(
                                'SUSE:SLE-15-{}'.format(sp), target_package,
                                src_srcinfo.verifymd5)
                            if good:
                                self.logger.info(
                                    "request sources come from SLE")
                                self.needs_release_manager = True
                                return good
                    # TODO Ugly save for 15.2 (n-2).
                    elif False and oldorigin.startswith('openSUSE:Leap:15.0'):
                        self.logger.info("Package was from %s in 15.0",
                                         oldorigin)
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
                self.needs_release_manager = True

            elif origin.startswith('SUSE:SLE-15'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                if src_project.startswith('SUSE:SLE-15'):
                    self.do_check_maintainer_review = False
                for v in ('15.0', '15.1'):
                    prj = 'openSUSE:Leap:{}:SLE-workarounds'.format(v)
                    if self.is_package_in_project(prj, target_package):
                        self.logger.info("found package in %s", prj)
                        if not self._check_matching_srcmd5(
                                prj, target_package, src_srcinfo.verifymd5):
                            self.logger.info(
                                "sources in %s are NOT identical",
                                self.rdiff_link(src_project, src_package,
                                                src_rev, prj, package))

                        self.needs_release_manager = True
                # submitted from :Update
                if origin == src_project:
                    self.logger.debug("submission origin ok")
                    return True
                elif origin.endswith(':GA') \
                    and src_project == origin[:-2]+'Update':
                    self.logger.debug("sle update submission")
                    return True

                # check  if submitted from higher SP
                priolist = [
                    'SUSE:SLE-15:', 'SUSE:SLE-15-SP1:', 'SUSE:SLE-15-SP2:',
                    'SUSE:SLE-15-SP3:'
                ]
                for i in range(len(priolist) - 1):
                    if origin.startswith(priolist[i]):
                        for prj in priolist[i + 1:]:
                            if src_project.startswith(prj):
                                self.logger.info(
                                    "submission from higher service pack %s:* ok",
                                    prj)
                                return True

                in_sle_origin = self._check_factory(target_package,
                                                    src_srcinfo, origin)
                if in_sle_origin:
                    self.logger.info(
                        'parallel submission, also in {}'.format(origin))
                    return True

                self.needs_release_manager = True
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
            else:
                self.logger.error("unhandled origin %s", origin)
                return False
        else:  # no origin
            # submission from SLE is ok
            if src_project.startswith('SUSE:SLE-15'):
                self.do_check_maintainer_review = False
                return True

            # new package submitted from Factory. Check if it was in
            # 42.3 before and skip maintainer review if so.
            subprj = src_project[len('openSUSE:Factory'):]
            # disabled for reference. Needed again for 16.0 probably
            if False and self.source_in_factory and target_project.startswith('openSUSE:Leap:15.0') \
                and self.is_package_in_project('openSUSE:Leap:42.3'+subprj, package):
                self.logger.info('package was in 42.3')
                self.do_check_maintainer_review = False
                return True

            is_fine_if_factory = True
            self.needs_release_manager = True

        if origin is None or not origin.startswith('SUSE:SLE-'):
            for p in (':Update', ':GA'):
                prj = 'SUSE:SLE-15' + p
                if self.is_package_in_project(prj, package):
                    self.logger.info('Package is in {}'.format(
                        self.rdiff_link(src_project, src_package, src_rev, prj,
                                        package)))
                    break

        is_in_factory = self.source_in_factory

        # we came here because none of the above checks find it good, so
        # let's see if the package is in Factory at least
        if is_in_factory is None:
            is_in_factory = self._check_factory(package, src_srcinfo)
        if is_in_factory:
            self.source_in_factory = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        elif is_in_factory is None:
            self.pending_factory_submission = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        else:
            if src_project.startswith('SUSE:SLE-15') \
                or src_project.startswith('openSUSE:Leap:15.'):
                self.needs_reviewteam = False
                self.needs_legal_review = False
            else:
                self.needs_reviewteam = True
                self.needs_legal_review = True
            self.source_in_factory = False

        if is_fine_if_factory:
            if self.source_in_factory:
                return True
            elif self.pending_factory_submission:
                return None
            elif not_in_factory_okish:
                self.needs_reviewteam = True
                self.needs_legal_review = True
                return True

        if self.override_allow:
            # Rather than decline, leave review open and ask release
            # manager for input via override comment.
            self.logger.info(
                'Comment `(at){} override accept` to force accept.'.format(
                    self.review_user))
            self.needs_release_manager = True
            return None

        return False

    def _check_factory(self,
                       target_package,
                       src_srcinfo,
                       target_project='openSUSE:Factory'):
        for subprj in ('', ':NonFree', ':Live'):
            prj = ''.join((target_project, subprj))
            good = self._check_matching_srcmd5(prj, target_package,
                                               src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests(prj, target_package,
                                                src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to %s", prj)
                return good

        return False

    def _check_project_and_request(self, project, target_package, src_srcinfo):
        good = self._check_matching_srcmd5(project, target_package,
                                           src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests(project, target_package,
                                            src_srcinfo.verifymd5)
        if good or good == None:
            return good
        return False

    def check_one_request(self, req):
        config = Config.get(self.apiurl, req.actions[0].tgt_project)
        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.needs_release_manager = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.do_check_maintainer_review = not self.ibs
        self.packages = {}

        request_ok = ReviewBot.ReviewBot.check_one_request(self, req)

        self.logger.debug("review result: %s", request_ok)
        if self.pending_factory_submission:
            self.logger.info(
                "submission is waiting for a Factory request to complete")
            creator = req.get_creator()
            bot_name = self.bot_name.lower()
            if self.automatic_submission and creator != bot_name:
                self.logger.info(
                    '@{}: this request would have been automatically created by {} after the Factory submission was accepted in order to eleviate the need to manually create requests for packages sourced from Factory'
                    .format(creator, bot_name))
        elif self.source_in_factory:
            self.logger.info(
                "perfect. the submitted sources are in or accepted for Factory"
            )
        elif self.source_in_factory == False:
            self.logger.warn("the submitted sources are NOT in Factory")

        if request_ok == False:
            self.logger.info(
                "NOTE: if you think the automated review was wrong here, please talk to the release team before reopening the request"
            )

        if self.do_comments:
            result = None
            if request_ok is None:
                state = 'seen'
            elif request_ok:
                state = 'done'
                result = 'accepted'
            else:
                state = 'done'
                result = 'declined'

            self.comment_write(state, result)

        add_review_groups = []
        if self.needs_release_manager:
            add_review_groups.append(self.release_manager_group
                                     or config.get(self.override_group_key))
        if self.needs_reviewteam:
            add_review_groups.append(self.review_team_group
                                     or config.get('review-team'))
        if self.needs_legal_review:
            add_review_groups.append(self.legal_review_group
                                     or config.get('legal-review-group'))
        if self.needs_check_source and self.check_source_group is not None:
            add_review_groups.append(self.check_source_group)

        for group in add_review_groups:
            if group is None:
                continue
            self.logger.info(
                "{0} needs review by [{1}](/group/show/{1})".format(
                    req.reqid, group))
            self.add_review(req, by_group=group)

        return request_ok

    def check_action__default(self, req, a):
        self.needs_release_manager = True
        return super(Leaper, self).check_action__default(req, a)
class Leaper(ReviewBot.ReviewBot):
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)

        # ReviewBot options.
        self.only_one_action = True
        self.request_default_return = True
        self.comment_handler = True

        self.do_comments = True

        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.needs_release_manager = False
        self.release_manager_group = None
        self.review_team_group = None
        self.legal_review_group = None
        self.must_approve_version_updates = False
        self.must_approve_maintenance_updates = False
        self.needs_check_source = False
        self.check_source_group = None
        self.automatic_submission = False

        # project => package list
        self.packages = {}

    def prepare_review(self):
        # update lookup information on every run

        if self.ibs:
            self.factory.parse_lookup('SUSE:SLE-15:GA')
            self.lookup_sle15 = self.factory.lookup.copy()
            return

        self.factory.parse_lookup('openSUSE:Leap:15.0')
        self.factory.parse_lookup('openSUSE:Leap:15.0:NonFree')
        self.lookup_150 = self.factory.lookup.copy()

    def get_source_packages(self, project, expand=False):
        """Return the list of packages in a project."""
        query = {'expand': 1} if expand else {}
        try:
            root = ET.parse(
                osc.core.http_GET(
                    osc.core.makeurl(self.apiurl, ['source', project],
                                     query=query))).getroot()
            packages = [i.get('name') for i in root.findall('entry')]
        except urllib2.HTTPError as e:
            # in case the project doesn't exist yet (like sle update)
            if e.code != 404:
                raise e
            packages = []

        return packages

    def is_package_in_project(self, project, package):
        if not project in self.packages:
            self.packages[project] = self.get_source_packages(project)
        return True if package in self.packages[project] else False

    def rdiff_link(self,
                   src_project,
                   src_package,
                   src_rev,
                   target_project,
                   target_package=None):
        if target_package is None:
            target_package = src_package

        return '[%(target_project)s/%(target_package)s](/package/rdiff/%(src_project)s/%(src_package)s?opackage=%(target_package)s&oproject=%(target_project)s&rev=%(src_rev)s)' % {
            'src_project': src_project,
            'src_package': src_package,
            'src_rev': src_rev,
            'target_project': target_project,
            'target_package': target_package,
        }

    def _check_same_origin(self, origin, project):

        if origin == 'FORK':
            return True

        if origin.startswith('Devel;'):
            (dummy, origin, dummy) = origin.split(';')

        return project.startswith(origin)

    def check_source_submission(self, src_project, src_package, src_rev,
                                target_project, target_package):
        super(Leaper,
              self).check_source_submission(src_project, src_package, src_rev,
                                            target_project, target_package)
        self.automatic_submission = False

        if src_project == target_project and src_package == target_package:
            self.logger.info('self submission detected')
            self.needs_release_manager = True
            return True

        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        origin = None

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" %
                             (src_project, src_package, src_rev))
            return False

        if self.ibs and target_project.startswith('SUSE:SLE'):

            if package in self.lookup_sle15:
                origin = self.lookup_sle15[package]

            origin_same = True
            if origin:
                origin_same = self._check_same_origin(origin, src_project)
                self.logger.info("expected origin is '%s' (%s)", origin,
                                 "unchanged" if origin_same else "changed")

            prj = 'openSUSE.org:openSUSE:Factory'
            # True or None (open request) are acceptable for SLE.
            self.source_in_factory = self._check_factory(
                package, src_srcinfo, prj)
            if self.source_in_factory is None:
                self.pending_factory_submission = True
            if self.source_in_factory is not False:
                return self.source_in_factory

            # got false. could mean package doesn't exist or no match
            if self.is_package_in_project(prj, package):
                self.logger.info('different sources in {}'.format(
                    self.rdiff_link(src_project, src_package, src_rev, prj,
                                    package)))

            prj = 'openSUSE.org:openSUSE:Leap:15.0'
            # TODO Ugly save for SLE-15-SP1.
            if False and self.is_package_in_project(prj, package):
                if self._check_factory(package, src_srcinfo, prj) is True:
                    self.logger.info('found source match in {}'.format(prj))
                else:
                    self.logger.info('different sources in {}'.format(
                        self.rdiff_link(src_project, src_package, src_rev, prj,
                                        package)))

            devel_project, devel_package = devel_project_get(
                self.apiurl, 'openSUSE.org:openSUSE:Factory', package)
            if devel_project is not None:
                # specifying devel package is optional
                if devel_package is None:
                    devel_package = package
                if self.is_package_in_project(devel_project, devel_package):
                    if self.factory._check_project(
                            devel_project, devel_package,
                            src_srcinfo.verifymd5) == True:
                        self.logger.info('matching sources in {}/{}'.format(
                            devel_project, devel_package))
                        return True
                    else:
                        self.logger.info('different sources in {}'.format(
                            self.rdiff_link(src_project, src_package, src_rev,
                                            devel_project, devel_package)))
            else:
                self.logger.info('no devel project found for {}/{}'.format(
                    'openSUSE.org:openSUSE:Factory', package))

            #self.logger.info('no matching sources in Factory, Leap:15.0, nor devel project')
            self.logger.info(
                'no matching sources in Factory, nor devel project')

            if origin_same is False:
                # Rather than decline, leave review open in-case of change and
                # ask release manager for input via override comment.
                self.logger.info(
                    'Comment `(at){} override accept` to force accept.'.format(
                        self.review_user))
                self.needs_release_manager = True
                return None

            return origin_same

        if package in self.lookup_150:
            origin = self.lookup_150[package]

        # obviously
        if src_project in ('openSUSE:Factory', 'openSUSE:Factory:NonFree'):
            self.source_in_factory = True

        is_fine_if_factory = False
        not_in_factory_okish = False
        if origin:
            origin_same = self._check_same_origin(origin, src_project)
            self.logger.info("expected origin is '%s' (%s)", origin,
                             "unchanged" if origin_same else "changed")
            if origin.startswith('Devel;'):
                if origin_same == False:
                    self.logger.debug("not submitted from devel project")
                    return False
                is_fine_if_factory = True
                not_in_factory_okish = True
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                # fall through to check history and requests
            elif origin.startswith('openSUSE:Factory'):
                # A large number of requests are created by hand that leaper
                # would have created via update_crawler.py. This applies to
                # other origins, but primary looking to let Factory submitters
                # know that there is no need to make manual submissions to both.
                # Since it has a lookup entry it is not a new package.
                self.automatic_submission = False
                if self.must_approve_version_updates:
                    self.needs_release_manager = True
                if origin == src_project:
                    self.source_in_factory = True
                    # no need to approve submissions from Factory if
                    # the lookup file points to Factory. Just causes
                    # spam for many maintainers #1393
                    self.do_check_maintainer_review = False
                is_fine_if_factory = True
                # fall through to check history and requests
            elif origin == 'FORK':
                is_fine_if_factory = True
                if not src_project.startswith('SUSE:SLE-'):
                    not_in_factory_okish = True
                    self.needs_check_source = True
                self.needs_release_manager = True
                # fall through to check history and requests
            # TODO Ugly save for 15.1 (n-1).
            elif False and origin.startswith('openSUSE:Leap:15.0'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                # submitted from :Update
                if origin_same:
                    self.logger.debug("submission from 15.0 ok")
                    return True
                # switching to sle package might make sense
                if src_project.startswith('SUSE:SLE-15'):
                    self.needs_release_manager = True
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self.factory._check_project(
                        'openSUSE:Leap:15.0:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 15.0")
                        return good
                    # check release requests too
                    good = self.factory._check_requests(
                        'openSUSE:Leap:15.0:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                if package in self.lookup_150:
                    oldorigin = self.lookup_150[package]
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    self.logger.info("Package was from %s in 15.0", oldorigin)
                    if oldorigin.startswith('openSUSE:Factory'):
                        # check if an attempt to switch to SLE package is made
                        for sp in ('SP1:GA', 'SP1:Update'):
                            good = self.factory._check_project(
                                'SUSE:SLE-15-{}'.format(sp), target_package,
                                src_srcinfo.verifymd5)
                            if good:
                                self.logger.info(
                                    "request sources come from SLE")
                                self.needs_release_manager = True
                                return good
                    # TODO Ugly save for 15.2 (n-2).
                    elif False and oldorigin.startswith('openSUSE:Leap:15.0'):
                        o = self.lookup_150[package]
                        self.logger.info("Package was from %s in 15.0", o)
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
                self.needs_release_manager = True

            elif origin.startswith('SUSE:SLE-15'):
                if self.must_approve_maintenance_updates:
                    self.needs_release_manager = True
                for v in ('15.0', ):
                    prj = 'openSUSE:Leap:{}:SLE-workarounds'.format(v)
                    if self.is_package_in_project(prj, target_package):
                        self.logger.info("found package in %s", prj)
                        if not self.factory._check_project(
                                prj, target_package, src_srcinfo.verifymd5):
                            self.logger.info(
                                "sources in %s are [NOT identical](%s)", prj,
                                self.rdiff_link(src_project, src_package,
                                                src_rev, prj, package))

                        self.needs_release_manager = True
                # submitted from :Update
                if origin == src_project:
                    self.logger.debug("submission origin ok")
                    return True
                elif origin.endswith(':GA') \
                    and src_project == origin[:-2]+'Update':
                    self.logger.debug("sle update submission")
                    return True

                # check  if submitted from higher SP
                priolist = [
                    'SUSE:SLE-15:', 'SUSE:SLE-15-SP1:', 'SUSE:SLE-15-SP2:',
                    'SUSE:SLE-15-SP3:'
                ]
                for i in range(len(priolist) - 1):
                    if origin.startswith(priolist[i]):
                        for prj in priolist[i + 1:]:
                            if src_project.startswith(prj):
                                self.logger.info(
                                    "submission from higher service pack %s:* ok",
                                    prj)
                                return True

                in_sle_origin = self._check_factory(target_package,
                                                    src_srcinfo, origin)
                if in_sle_origin:
                    self.logger.info(
                        'parallel submission, also in {}'.format(origin))
                    return True

                self.needs_release_manager = True
                # the release manager needs to review attempts to upgrade to Factory
                is_fine_if_factory = True
            else:
                self.logger.error("unhandled origin %s", origin)
                return False
        else:  # no origin
            # submission from SLE is ok
            if src_project.startswith('SUSE:SLE-15'):
                self.do_check_maintainer_review = False
                return True

            # new package submitted from Factory. Check if it was in
            # 42.3 before and skip maintainer review if so.
            subprj = src_project[len('openSUSE:Factory'):]
            if self.source_in_factory and target_project.startswith('openSUSE:Leap:15.0') \
                and self.is_package_in_project('openSUSE:Leap:42.3'+subprj, package):
                self.logger.info('package was in 42.3')
                self.do_check_maintainer_review = False
                return True

            is_fine_if_factory = True
            self.needs_release_manager = True

        if origin is None or not origin.startswith('SUSE:SLE-'):
            for p in (':Update', ':GA'):
                prj = 'SUSE:SLE-15' + p
                if self.is_package_in_project(prj, package):
                    self.logger.info('Package is in {}'.format(
                        self.rdiff_link(src_project, src_package, src_rev, prj,
                                        package)))
                    break

        is_in_factory = self.source_in_factory

        # we came here because none of the above checks find it good, so
        # let's see if the package is in Factory at least
        if is_in_factory is None:
            is_in_factory = self._check_factory(package, src_srcinfo)
        if is_in_factory:
            self.source_in_factory = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        elif is_in_factory is None:
            self.pending_factory_submission = True
            self.needs_reviewteam = False
            self.needs_legal_review = False
        else:
            if src_project.startswith('SUSE:SLE-15') \
                or src_project.startswith('openSUSE:Leap:15.'):
                self.needs_reviewteam = False
                self.needs_legal_review = False
            else:
                self.needs_reviewteam = True
                self.needs_legal_review = True
            self.source_in_factory = False

        if is_fine_if_factory:
            if self.source_in_factory:
                return True
            elif self.pending_factory_submission:
                return None
            elif not_in_factory_okish:
                self.needs_reviewteam = True
                self.needs_legal_review = True
                return True

        if self.override_allow:
            # Rather than decline, leave review open and ask release
            # manager for input via override comment.
            self.logger.info(
                'Comment `(at){} override accept` to force accept.'.format(
                    self.review_user))
            self.needs_release_manager = True
            return None

        return False

    def _check_factory(self,
                       target_package,
                       src_srcinfo,
                       target_project='openSUSE:Factory'):
        for subprj in ('', ':NonFree', ':Live'):
            prj = ''.join((target_project, subprj))
            good = self.factory._check_project(prj, target_package,
                                               src_srcinfo.verifymd5)
            if good:
                return good
            good = self.factory._check_requests(prj, target_package,
                                                src_srcinfo.verifymd5)
            if good or good == None:
                self.logger.debug("found request to %s", prj)
                return good

        return False

    def _check_project_and_request(self, project, target_package, src_srcinfo):
        good = self.factory._check_project(project, target_package,
                                           src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests(project, target_package,
                                            src_srcinfo.verifymd5)
        if good or good == None:
            return good
        return False

    def check_one_request(self, req):
        api = self.staging_api(req.actions[0].tgt_project)
        config = self.staging_config[api.project]
        self.needs_legal_review = False
        self.needs_reviewteam = False
        self.needs_release_manager = False
        self.pending_factory_submission = False
        self.source_in_factory = None
        self.do_check_maintainer_review = not self.ibs
        self.packages = {}

        request_ok = ReviewBot.ReviewBot.check_one_request(self, req)
        if self.do_check_maintainer_review:
            has_correct_maintainer = self.maintbot.check_one_request(req)
            self.logger.debug("has_correct_maintainer: %s",
                              has_correct_maintainer)

        self.logger.debug("review result: %s", request_ok)
        if self.pending_factory_submission:
            self.logger.info(
                "submission is waiting for a Factory request to complete")
            creator = req.get_creator()
            bot_name = self.bot_name.lower()
            if self.automatic_submission and creator != bot_name:
                self.logger.info(
                    '@{}: this request would have been automatically created by {} after the Factory submission was accepted in order to eleviate the need to manually create requests for packages sourced from Factory'
                    .format(creator, bot_name))
        elif self.source_in_factory:
            self.logger.info(
                "the submitted sources are in or accepted for Factory")
        elif self.source_in_factory == False:
            self.logger.info("the submitted sources are NOT in Factory")

        if request_ok == False:
            self.logger.info(
                "NOTE: if you think the automated review was wrong here, please talk to the release team before reopening the request"
            )

        if self.do_comments:
            result = None
            if request_ok is None:
                state = 'seen'
            elif request_ok:
                state = 'done'
                result = 'accepted'
            else:
                state = 'done'
                result = 'declined'
            # Since leaper calls other bots (like maintbot) comments may
            # sometimes contain identical lines (like for unhandled requests).
            self.comment_handler_lines_deduplicate()
            self.comment_write(state, result)

        add_review_groups = []
        if self.needs_release_manager:
            add_review_groups.append(self.release_manager_group
                                     or config.get(self.override_group_key))
        if self.needs_reviewteam:
            add_review_groups.append(self.review_team_group
                                     or config.get('review-team'))
        if self.needs_legal_review:
            add_review_groups.append(self.legal_review_group
                                     or config.get('legal-review-group'))
        if self.needs_check_source and self.check_source_group is not None:
            add_review_groups.append(self.check_source_group)

        for group in add_review_groups:
            if group is None:
                continue
            self.logger.info(
                "{0} needs review by [{1}](/group/show/{1})".format(
                    req.reqid, group))
            self.add_review(req, by_group=group)

        return request_ok

    def check_action__default(self, req, a):
        self.needs_release_manager = True
        if self.ibs:
            self.do_check_maintainer_review = False
        return super(Leaper, self).check_action__default(req, a)
class TestFactorySourceAccept(unittest.TestCase):

    def setUp(self):
        """
        Initialize the configuration
        """

        httpretty.reset()
        httpretty.enable()

        oscrc = os.path.join(FIXTURES, 'oscrc')
        osc.core.conf.get_config(override_conffile=oscrc,
                                 override_no_keyring=True,
                                 override_no_gnome_keyring=True)
        #osc.conf.config['debug'] = 1

        logging.basicConfig()
        self.logger = logging.getLogger(__file__)
        self.logger.setLevel(logging.DEBUG)

        self.checker = FactorySourceChecker(apiurl = APIURL, \
                user = '******', \
                logger = self.logger)

    def test_accept_request(self):

        httpretty.register_uri(httpretty.GET,
            APIURL + "/request/770001",
            body = """
                <request id="770001">
                  <action type="submit">
                    <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                    <target project="openSUSE:13.2" package="timezone"/>
                  </action>
                  <state name="new" who="factory-source" when="2014-10-08T12:06:07">
                    <comment>...</comment>
                  </state>
                  <review state="new" by_user="******"/>
                  <description>...</description>
                </request>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/Base:System/timezone?rev=481ecbe0dfc63ece3a1f1b5598f7d96c&view=info"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="481ecbe0dfc63ece3a1f1b5598f7d96c"
                    srcmd5="481ecbe0dfc63ece3a1f1b5598f7d96c"
                    verifymd5="67bac34d29d70553239d33aaf92d2fdd">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)
        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/timezone?view=info"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="89"
                    vrev="1"
                    srcmd5="a36605617cbeefa8168bf0ccf3058074"
                    verifymd5="a36605617cbeefa8168bf0ccf3058074">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/timezone/_history?limit=5"),
            match_querystring = True,
            body = """
                <sourceinfo package="timezone"
                    rev="89"
                    vrev="1"
                    srcmd5="a36605617cbeefa8168bf0ccf3058074"
                    verifymd5="a36605617cbeefa8168bf0ccf3058074">
                  <filename>timezone.spec</filename>
                </sourceinfo>
            """)
        httpretty.register_uri(httpretty.GET,
            rr("/search/request?match=%28state%2F%40name%3D%27new%27+or+state%2F%40name%3D%27review%27%29+and+%28action%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+submit%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+action%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27+or+submit%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27%29+and+%28action%2Ftarget%2F%40package%3D%27timezone%27+or+submit%2Ftarget%2F%40package%3D%27timezone%27+or+action%2Fsource%2F%40package%3D%27timezone%27+or+submit%2Fsource%2F%40package%3D%27timezone%27%29+and+action%2F%40type%3D%27submit%27"),
            match_querystring = True,
            responses = [
                httpretty.Response( body = """
                    <collection matches="1">
                      <request id="254684">
                        <action type="submit">
                          <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                          <target project="openSUSE:Factory" package="timezone"/>
                        </action>
                        <state name="review" who="factory-auto" when="2014-10-08T11:55:56">
                          <comment>...</comment>
                        </state>
                        <description> ... </description>
                      </request>
                    </collection>
                    """),
                httpretty.Response( body = """
                    <collection matches="1">
                      <request id="254684">
                        <action type="submit">
                          <source project="Base:System" package="timezone" rev="481ecbe0dfc63ece3a1f1b5598f7d96c"/>
                          <target project="openSUSE:Factory" package="timezone"/>
                        </action>
                        <state name="new" who="factory-auto" when="2014-10-08T11:55:56">
                          <comment>...</comment>
                        </state>
                        <description> ... </description>
                      </request>
                    </collection>
                    """)
                ])

        result = { 'status' : None }

        def change_request(result, method, uri, headers):
            u = urlparse.urlparse(uri)
            if u.query == 'newstate=accepted&cmd=changereviewstate&by_user=factory-source':
                result['status'] = True
            return (200, headers, '<status code="blah"/>')

        httpretty.register_uri(httpretty.POST,
            APIURL + "/request/770001",
            body = lambda method, uri, headers: change_request(result, method, uri, headers))

        # first time request is in in review
        self.checker.set_request_ids(['770001'])
        self.checker.check_requests()

        self.assertEqual(result['status'], None)

        # second time request is in state new so we can accept
        self.checker.set_request_ids(['770001'])
        self.checker.check_requests()

        self.assertTrue(result['status'])

    def test_source_not_in_factory(self):

        httpretty.register_uri(httpretty.GET,
            rr("/search/request?match=state/@name='review'+and+review[@by_user='******'+and+@state='new']&withhistory=1"),
            match_querystring = True,
            body = """
                <collection matches="1">
                    <request id="261411">
                      <action type="maintenance_incident">
                        <source project="home:lnussel:branches:openSUSE:Backports:SLE-12" package="plan" rev="71e76daf2c2e9ddb0b9208f54a14f608"/>
                        <target project="openSUSE:Maintenance" releaseproject="openSUSE:Backports:SLE-12"/>
                      </action>
                      <state name="review" who="maintbot" when="2014-11-13T13:22:02">
                        <comment></comment>
                      </state>
                      <review state="accepted" when="2014-11-13T13:22:02" who="maintbot" by_user="******">
                        <comment>accepted</comment>
                        <history who="maintbot" when="2014-11-13T16:43:09">
                          <description>Review got accepted</description>
                          <comment>accepted</comment>
                        </history>
                      </review>
                      <review state="new" by_user="******"/>
                      <history who="lnussel" when="2014-11-13T13:22:02">
                        <description>Request created</description>
                        <comment>test update</comment>
                      </history>
                      <history who="maintbot" when="2014-11-13T16:43:08">
                        <description>Request got a new review request</description>
                      </history>
                      <description>test update</description>
                    </request>
                </collection>
            """)

        httpretty.register_uri(httpretty.GET,
            APIURL + "/request/261411",
            body = """
                <request id="261411">
                  <action type="maintenance_incident">
                    <source project="home:lnussel:branches:openSUSE:Backports:SLE-12" package="plan" rev="71e76daf2c2e9ddb0b9208f54a14f608"/>
                    <target project="openSUSE:Maintenance" releaseproject="openSUSE:Backports:SLE-12"/>
                  </action>
                  <state name="review" who="maintbot" when="2014-11-13T13:22:02">
                    <comment></comment>
                  </state>
                  <review state="accepted" when="2014-11-13T13:22:02" who="maintbot" by_user="******">
                    <comment>accepted</comment>
                    <history who="maintbot" when="2014-11-13T16:43:09">
                      <description>Review got accepted</description>
                      <comment>accepted</comment>
                    </history>
                  </review>
                  <review state="new" by_user="******"/>
                  <history who="lnussel" when="2014-11-13T13:22:02">
                    <description>Request created</description>
                    <comment>test update</comment>
                  </history>
                  <history who="maintbot" when="2014-11-13T16:43:08">
                    <description>Request got a new review request</description>
                  </history>
                  <description>test update</description>
                </request>
            """)

        httpretty.register_uri(httpretty.GET,
            APIURL + "/source/home:lnussel:branches:openSUSE:Backports:SLE-12/plan",
            body = """
                <directory name="plan" rev="1" vrev="1" srcmd5="b4ed19dc30c1b328168bc62a81ec6998">
                  <linkinfo project="home:lnussel:plan" package="plan" srcmd5="7a2353f73b29dba970702053229542a0" baserev="7a2353f73b29dba970702053229542a0" xsrcmd5="71e76daf2c2e9ddb0b9208f54a14f608" lsrcmd5="b4ed19dc30c1b328168bc62a81ec6998" />
                  <entry name="_link" md5="91f81d88456818a18a7332999fb2da18" size="125" mtime="1415807350" />
                  <entry name="plan.spec" md5="b6814215f6d2e8559b43de9a214b2cbd" size="8103" mtime="1413627959" />
                </directory>

            """)

        httpretty.register_uri(httpretty.GET,
            rr("/source/openSUSE:Factory/plan?view=info"),
            match_querystring = True,
            status = 404,
            body = """
                <status code="unknown_package">
                    <summary>openSUSE:Factory/plan</summary>
                </status>
            """)

        httpretty.register_uri(httpretty.GET,
            rr("/search/request?match=%28state%2F%40name%3D%27new%27+or+state%2F%40name%3D%27review%27%29+and+%28action%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+submit%2Ftarget%2F%40project%3D%27openSUSE%3AFactory%27+or+action%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27+or+submit%2Fsource%2F%40project%3D%27openSUSE%3AFactory%27%29+and+%28action%2Ftarget%2F%40package%3D%27plan%27+or+submit%2Ftarget%2F%40package%3D%27plan%27+or+action%2Fsource%2F%40package%3D%27plan%27+or+submit%2Fsource%2F%40package%3D%27plan%27%29+and+action%2F%40type%3D%27submit%27"),
            match_querystring = True,
            body = """
                <collection matches="0">
                </collection>
            """)

        result = { 'factory_source_declined' : None }

        def change_request(result, method, uri, headers):
            u = urlparse.urlparse(uri)
            if u.query == 'newstate=declined&cmd=changereviewstate&by_user=factory-source':
                result['factory_source_declined'] = True
            return (200, headers, '<status code="ok"/>')

        httpretty.register_uri(httpretty.POST,
            APIURL + "/request/261411",
            body = lambda method, uri, headers: change_request(result, method, uri, headers))

        self.checker.requests = []
        self.checker.set_request_ids_search_review()
        self.checker.check_requests()

        self.assertTrue(result['factory_source_declined'])
Example #22
0
class Leaper(ReviewBot.ReviewBot):
    def __init__(self, *args, **kwargs):
        ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
        self.maintbot = MaintenanceChecker(*args, **kwargs)
        # for FactorySourceChecker
        self.factory = FactorySourceChecker(*args, **kwargs)

        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False

    def prepare_review(self):

        # update lookup information on every run
        self.factory.parse_lookup('openSUSE:Leap:42.2')
        self.factory.parse_lookup('openSUSE:Leap:42.2:NonFree')
        self.lookup_422 = self.factory.lookup.copy()
        self.factory.lookup = {}
        self.factory.parse_lookup('openSUSE:Leap:42.1:Update')
        self.lookup_421 = self.factory.lookup.copy()
        self.factory.lookup = {}

    def check_source_submission(self, src_project, src_package, src_rev,
                                target_project, target_package):
        self.logger.info("%s/%s@%s -> %s/%s" %
                         (src_project, src_package, src_rev, target_project,
                          target_package))
        src_srcinfo = self.get_sourceinfo(src_project, src_package, src_rev)
        package = target_package

        if src_srcinfo is None:
            # source package does not exist?
            # handle here to avoid crashing on the next line
            self.logger.warn("Could not get source info for %s/%s@%s" %
                             (src_project, src_package, src_rev))
            return False

        origin = None
        if package in self.lookup_422:
            origin = self.lookup_422[package]

        if origin:
            self.logger.debug("origin {}".format(origin))
            if origin.startswith('Devel;'):
                self.needs_reviewteam = True
                (dummy, origin, dummy) = origin.split(';')
            if origin == src_project:
                self.logger.debug("exact match")
                return True
            elif origin.startswith('openSUSE:Factory'):
                return self._check_factory(target_package, src_srcinfo)
            elif origin.startswith('openSUSE:Leap:42.1'):
                # submitted from :Update
                if src_project.startswith(origin):
                    self.logger.debug("match 42.1")
                    return True
                # submitted from elsewhere but is in :Update
                else:
                    good = self.factory._check_project(
                        'openSUSE:Leap:42.1:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good:
                        self.logger.info("submission found in 42.1")
                        return good
                    # check release requests too
                    good = self.factory._check_requests(
                        'openSUSE:Leap:42.1:Update', target_package,
                        src_srcinfo.verifymd5)
                    if good or good == None:
                        self.logger.debug("found request")
                        return good
                # let's see where it came from before
                if package in self.lookup_421:
                    oldorigin = self.lookup_421[package]
                    self.logger.debug("oldorigin {}".format(oldorigin))
                    # Factory. So it's ok to keep upgrading it to Factory
                    # TODO: whitelist packages where this is ok and block others?
                    if oldorigin.startswith('openSUSE:Factory'):
                        if src_project == oldorigin:
                            self.logger.debug(
                                "Upgrade to Factory again. Submitted from Factory"
                            )
                            return True
                        good = self._check_factory(target_package, src_srcinfo)
                        if good or good == None:
                            self.logger.debug(
                                "Upgrade to Factory again. It's in Factory")
                            return good
                        # or maybe in SP2?
                        good = self.factory._check_project(
                            'SUSE:SLE-12-SP2:GA', target_package,
                            src_srcinfo.verifymd5)
                        if good:
                            self.logger.debug("hope it's ok to change to SP2")
                            return good
                # else other project or FORK, fall through

            elif origin.startswith('SUSE:SLE-12'):
                # submitted from :Update
                if src_project.startswith(origin):
                    self.logger.debug("match sle")
                    return True
                # submitted from higher SP
                if origin.startswith('SUSE:SLE-12'):
                    if src_project.startswith('SUSE:SLE-12-SP1') \
                        or src_project.startswith('SUSE:SLE-12-SP2'):
                        self.logger.debug("higher service pack ok")
                        return True
            # else other project or FORK, fall through

            # we came here because none of the above checks find it good, so
            # let's see if the package is in Factory at least
            is_in_factory = self._check_factory(target_package, src_srcinfo)
            if is_in_factory:
                self.source_in_factory = True
            elif is_in_factory is None:
                self.pending_factory_submission = True
            else:
                if not src_project.startswith('SUSE:SLE-12'):
                    self.needs_reviewteam = True

        else:  # no origin
            # SLE and Factory are ok
            if src_project.startswith('SUSE:SLE-12') \
                or src_project.startswith('openSUSE:Factory'):
                return True
            # submitted from elsewhere, check it's in Factory
            good = self._check_factory(target_package, src_srcinfo)
            if good:
                self.source_in_factory = True
                return True
            elif good == None:
                self.pending_factory_submission = True
                return good
            # or maybe in SP2?
            good = self.factory._check_project('SUSE:SLE-12-SP2:GA',
                                               target_package,
                                               src_srcinfo.verifymd5)
            if good:
                return good

        return False

    def _check_factory(self, target_package, src_srcinfo):
        good = self.factory._check_project('openSUSE:Factory', target_package,
                                           src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests('openSUSE:Factory', target_package,
                                            src_srcinfo.verifymd5)
        if good or good == None:
            self.logger.debug("found request to Factory")
            return good
        good = self.factory._check_project('openSUSE:Factory:NonFree',
                                           target_package,
                                           src_srcinfo.verifymd5)
        if good:
            return good
        good = self.factory._check_requests('openSUSE:Factory:NonFree',
                                            target_package,
                                            src_srcinfo.verifymd5)
        if good or good == None:
            self.logger.debug("found request to Factory:NonFree")
            return good
        return False

    def check_one_request(self, req):
        self.review_messages = self.DEFAULT_REVIEW_MESSAGES.copy()
        self.needs_reviewteam = False
        self.pending_factory_submission = False
        self.source_in_factory = False

        if len(req.actions) != 1:
            msg = "only one action per request please"
            self.review_messages['declined'] = msg
            return False

        # if the fallback reviewer created the request she probably
        # knows what she does :-)
        if self.fallback_user and req.get_creator() == self.fallback_user:
            self.logger.debug("skip fallback review")
            return True

        has_upstream_sources = ReviewBot.ReviewBot.check_one_request(self, req)
        has_correct_maintainer = self.maintbot.check_one_request(req)

        # not reviewed yet?
        if has_upstream_sources is None:
            return None

        self.logger.debug("upstream sources: {}, maintainer ok: {}".format(
            has_upstream_sources, has_correct_maintainer))

        if self.needs_reviewteam:
            add_review = True
            self.logger.debug("%s needs review by opensuse-review-team" %
                              req.reqid)
            for r in req.reviews:
                if r.by_group == 'opensuse-review-team':
                    add_review = False
                    self.logger.debug(
                        "opensuse-review-team already is a reviewer")
                    break
            if add_review:
                if self.add_review(req,
                                   by_group="opensuse-review-team") != True:
                    self.review_messages[
                        'declined'] += '\nadding opensuse-review-team failed'
                    return False

        if has_upstream_sources != True or has_correct_maintainer != True:
            if has_upstream_sources != True:
                self.review_messages['declined'] += '\nOrigin project changed'
                pkg = req.actions[0].tgt_package
                if pkg in self.lookup_422:
                    self.review_messages['declined'] += '(was {})'.format(
                        self.lookup_422[pkg])
                if self.source_in_factory:
                    self.review_messages[
                        'declined'] += '\nsource is in Factory'
                if self.pending_factory_submission:
                    self.review_messages[
                        'declined'] += '\na submission to Factory is pending'
                    self.logger.debug(
                        "origin changed but waiting for Factory submission to complete"
                    )
                    # FXIME: we should add the human reviewer here
                    # and leave a comment
                    return None
            # shouldn't happen actually
            if has_correct_maintainer != True:
                self.review_messages['declined'] += '\nMaintainer check failed'
            return False

        return True

    def check_action__default(self, req, a):
        # decline all other requests for fallback reviewer
        self.logger.debug("auto decline request type %s" % a.type)
        return False