Example #1
0
def autoassign_rras(config):
    """This will search through unassigned RRA bugs and assign them automatically"""
    bcfg = config['bugzilla']

    # If no API key has been specified, just skip this
    if len(bcfg['api_key']) == 0:
        return

    b = bugzilla.Bugzilla(url=bcfg['url'], api_key=bcfg['api_key'])

    try:
        with open(bcfg['cache'], 'rb') as f:
            assign_list = pickle.load(f)
    except FileNotFoundError:
        debug("no current autoassign list found, using configured defaults")
        assign_list = list(bcfg['autoassign'])

    # Do we have any RRA in the queue?
    terms = [{
        'product': bcfg['product']
    }, {
        'component': bcfg['component']
    }, {
        'status': 'NEW'
    }, {
        'status': 'UNCONFIRMED'
    }]

    bugs = b.search_bugs(terms)['bugs']
    try:
        bugzilla.DotDict(bugs[-1])
        debug("Found {} unassigned RRA(s). Assigning work!".format(len(bugs)))
        for bug in bugs:
            # Is this a valid rra request bug?
            if bug['whiteboard'].startswith('autoentry'):
                debug("{} is not an RRA, skipping".format(bug['id']))
                continue
            # Next assignee in the list, rotate
            assignee = assign_list.pop()
            assign_list.insert(0, assignee)
            bug_up = bugzilla.DotDict()
            bug_up.assigned_to = assignee
            bug_up.status = 'ASSIGNED'
            try:
                debug("Updating bug {} assigning {}".format(
                    bug['id'], assignee))
                b.put_bug(bug['id'], bug_up)
            except Exception as e:
                debug("Failed to update bug {}: {}".format(bug['id'], e))

        with open(bcfg['cache'], 'wb') as f:
            pickle.dump(assign_list, f)

    except IndexError:
        debug("No unassigned RRAs")
Example #2
0
def fill_bug(config, nags, source):
    bcfg = config['bugzilla']
    b = bugzilla.Bugzilla(url=bcfg['url'], api_key=bcfg['api_key'])

    #Did we already report this?
    terms = [{
        'product': bcfg['product']
    }, {
        'component': bcfg['component']
    }, {
        'creator': bcfg['creator']
    }, {
        'whiteboard': 'autoentry'
    }, {
        'resolution': ''
    }, {
        'status': 'NEW'
    }, {
        'status': 'ASSIGNED'
    }, {
        'status': 'REOPENED'
    }, {
        'status': 'UNCONFIRMED'
    }, {
        'whiteboard': 'rra2json={}'.format(source)
    }]

    bugs = b.search_bugs(terms)['bugs']
    try:
        bugzilla.DotDict(bugs[-1])
        debug("bug for {} is already present, not re-filling".format(source))
        return
    except IndexError:
        pass

    #If not, report now
    bug = bugzilla.DotDict()
    bug.product = bcfg['product']
    bug.component = bcfg['component']
    bug.summary = "There are {} issues with an RRA".format(len(nags))
    bug.description = json.dumps(nags)
    bug.whiteboard = 'autoentry rra2json={}'.format(source)
    try:
        ret = b.post_bug(bug)
    except e:
        debug("Filling bug failed: {}".format(e))
    debug("Filled bug {} {}".format(source, ret))
Example #3
0
def autocasa(bapi, capi, bcfg, ccfg, dry_run):
    """
    This will search through bugs and update CASA accordingly.
    @bcfg: bugzilla configuration dict
    @ccfg: casa configuration dict
    """

    bcfg_va = bcfg.get("va")
    bcfg_rra = bcfg.get("rra")

    # Look for bugs that are up to ccfg.lookup_period_in_days days old
    lookup_period = (datetime.now() - timedelta(days=ccfg.get("lookup_period_in_days"))).strftime("%Y-%m-%dT%H:%M:%SZ")

    # Look for all registered products, for that lookup_period
    terms = [
        {"product": bcfg_va.get("product")},
        {"product": bcfg_rra.get("product")},
        {"component": bcfg_va.get("component")},
        {"component": bcfg_rra.get("component")},
        {"last_change_time": lookup_period},
        {"creator": ccfg.get("bot_email")},
    ]
    bugs = bapi.search_bugs(terms)["bugs"]
    logger.debug("Analyzing {} bugs...".format(len(bugs)))

    for bug in bugs:
        logger.debug("Processing bug {}...".format(bug.get("id")))
        # Get casa project id and other metadata
        comments = bapi.get_comments(bug.get("id"))["bugs"][str(bug.get("id"))]["comments"]
        casa_data = capi.parse_casa_comment(comments[0]["text"])
        project_id = casa_data.get("project_id")
        if (len(casa_data)) == 0:
            logger.warning("Could not find any CASA data in comment 0 even thus this comment was created by CASA!")
            continue

        # STEP 1
        # Some basic checks that we can update that project
        ## Have bugzilla support?
        casa_project = capi.casa_get_project(casa_data.get("project_id"))
        if casa_project["syncedToIntegrations"].get("bugzilla") is not True:
            logger.warning("Project {} has no bugzilla integration, skipping!".format(project_id))
            continue

        # Ensure this project cares about security and thus has a security tab/channel
        if (casa_project.get("securityPrivacy") is None) or (casa_project["securityPrivacy"].get("security") is None):
            logger.warning("Project {} has no securityPrivacy.security component, skipping!".format(project_id))
            continue

        # Set a shorthands for our tab
        casa_project_security = casa_project["securityPrivacy"]["security"]
        casa_status = casa_project_security.get("status")

        # STEP 2
        # Is already approved/disapproved in some way?
        ## This means Bugzilla cannot override a status already set to done
        ## It will override "none" or "rejected" as necessary
        if casa_status["decision"] == "rejected" and not (bug.get("resolution") in ["WONTFIX", "INCOMPLETE"]):
            logger.warning(
                "Project {} already has a security status set ({}), but we're allowing override".format(
                    project_id, casa_status["decision"]
                )
            )
            pass
        elif casa_status["decision"] != "none":
            logger.warning(
                "Project {} already has a security status set ({}) and this cannot be reverted, skipping!".format(
                    project_id, casa_status["decision"]
                )
            )
            continue

        # XXX Temporary fix so that we do not re-set none status which can trigger email notifications,
        # until INVALID/DUPLICATE get their own status
        if casa_status["decision"] == "none" and (bug.get("resolution") in ["INVALID", "DUPLICATE", "INACTIVE"]):
            logger.warning("Project {} is already in status 'none' and will not be modified".format(project_id))
            continue

        # STEP 3
        # Check who's to be assigned to the project in Casa
        ## Only try this if the assignee looks like a Mozilla-corp email as we know this will otherwise fail
        delegator_id = None
        if bug.get("assigned_to").endswith("@mozilla.com"):
            try:
                delegator = capi.find_delegator(bug.get("assigned_to"))
            except IndexError:
                delegator = None
                logger.warning("No CASA delegator for Bugzilla user {}".format(bug.get("assigned_to")))
            else:
                delegator_id = delegator.get("id")

        try:
            deciding_approver = casa_status["decidingApprover"]["id"]
        except TypeError:
            # It's possible that the decidingApprover is empty in Casa
            deciding_approver = None
            logger.debug("No decidingApprover id present in CASA, internally setting deciding_approver to None")

        ## If lookup failed in any way, use whomever is already assigned by Casa
        if delegator_id is None:
            logger.warning(
                "Could not match Bugzilla assignee: {} with Casa, " "using defaults".format(bug.get("assigned_to"))
            )
            delegator_id = deciding_approver
            # Everything failed, we have no one to assign to. Warn and skip..
            if delegator_id is None:
                logger.warning(
                    "Could not find a valid delegator_id, this means we don't know whom to delegate to. "
                    "Project {} will NOT be assigned in CASA (skipping).".format(project_id)
                )
                continue
        elif delegator_id != deciding_approver:
            ## Set the new assignee if lookup worked
            if not dry_run:
                try:
                    res = capi.set_delegator(project_id, delegator_id)
                    logger.info(
                        "Setting new assignee/delegator in CASA to {} ({}) for project {}".format(
                            delegator_id, bug.get("assigned_to"), project_id
                        )
                    )
                except Exception as e:
                    logger.error('Attempt to set CASA ticket delegator failed : {}'.format(e))
            else:
                logger.info(
                    "Would attempt to set assignee/delegator in CASA to {} ({}) for project {}"
                    "(dry run prevented this)".format(delegator_id, bug.get("assigned_to"), project_id)
                )
        else:
            logger.info(
                "Assignee/delegator in CASA is already correct, no changes made "
                "{} ({}) for project {})".format(delegator_id, bug.get("assigned_to"), project_id)
            )

        # STEP 4
        # The project also needs to be in approverReview step/state in order for us to be able to set a delegator, so
        # ensure that here
        if casa_project_security["status"].get("step") != "approverReview":
            logger.info(
                "Project {} is not in approverReview state ({})".format(project_id, casa_project_security["status"])
            )
            if not dry_run:
                capi.set_project_step(project_id, channel="security", step="approverReview")
            else:
                logger.debug("Would set project {} step to approverReview (dry_run prevented this)".format(project_id))

        # STEP 5
        # Update the project status if the bug has been closed in some way
        if bug.get("status") in ["RESOLVED", "VERIFIED", "CLOSED"]:
            if not dry_run:
                if bug.get("resolution") in ["WONTFIX", "INCOMPLETE"]:
                    needinfo = {"requestee": bcfg.get("needinfo"), "name": "needinfo", "status": "?", "type_id": 800}
                    bug_up = bugzilla.DotDict()
                    bug_up.flags = [needinfo]
                    bapi.put_bug(bug.get("id"), bug_up)
                    # Override delegator to the risk manager if needed
                    try:
                        ts_delegator = capi.find_delegator(bcfg.get("needinfo"))
                        delegator_id = ts_delegator.get("id")
                        # Set the new delegator here so that casa_set_status() works for this delegator_id
                        # This is because Biztera will reset the project status when the delegator is changed, but also
                        # does not allow changing the project status with "another" delegator. This means the steps must
                        # always be:
                        # 1) change delegator (this will reset status)
                        # 2) change project status to the desired status
                        capi.set_delegator(project_id, delegator_id)
                    except IndexError:
                        logger.warning(
                            "No CASA delegator for Bugzilla user {}, using previous delegator".format(
                                bcfg.get("needinfo")
                            )
                        )
                    logger.info(
                        "Will inform risk manager {} of resolution state for bug {} "
                        "(and delegate CASA ticket to this user)".format(bcfg.get("needinfo"), bug.get("id"))
                    )
                try:
                    res = capi.casa_set_status(project_id, delegator_id, bug.get("resolution"))
                    logger.info(
                        "CASA API Updated status for project {} to {}: {} (delegator: {})".format(
                            project_id, bug.get("resolution"), res, delegator_id
                        )
                    )
                except Exception as e:
                    logger.error('Attempt to set CASA ticket status failed : {}'.format(e))
            else:
                logger.info(
                    "Would attempt to set status {} on project {} for bug {}{}"
                    " (dry run prevented this)".format(
                        bug.get("resolution"), casa_data.get("url"), bcfg.get("url")[:-5], bug.get("id")
                    )
                )
                if bug.get("resolution") in ["WONTFIX", "INCOMPLETE"]:
                    logger.info(
                        "Would have informed risk manager {} of resolution state for bug {}".format(
                            bcfg.get("needinfo"), bug.get("id")
                        )
                    )
        else:
            logger.debug(
                "Would not set CASA status because this bug is not in a resolved state yet: "
                "{}{}".format(bcfg.get("url")[:-5], bug.get("id"))
            )

    logger.debug("Casa analysis completed")
Example #4
0
def autoassign(bapi, capi, bcfg, ccfg, fcfg, dry_run):
    """
    This will search through unassigned bugs and assign them automatically.
    @bcfg: bugzilla configuration dict
    @ccfg: casa configuration dict
    @fcfg: foxsec configuration dict
    """
    global logger

    reset_assignees = False  # Controls if we're going to rewrite the cache that record who's the next assignee or not
    foxsec_keywords = fcfg.get("keywords")
    try:
        with open(bcfg.get("cache"), "rb") as f:
            (assign_list, assign_hash) = pickle.load(f)
            if set(assign_list) != set(bcfg.get("assignees")):
                logger.info("List of assignees changed, resetting list!")
                reset_assignees = True
    except FileNotFoundError:
        reset_assignees = True

    if reset_assignees:
        assign_hash = bcfg.get("assignees")
        assign_list = assign_hash[:]
        logger.info("Configuring defaults for the NEW assignment list: {}".format(assign_hash))

    # Do we have any bugs in the queue?
    terms = [
        {"product": bcfg.get("product")},
        {"component": bcfg.get("component")},
        {"status": "NEW"},
        {"status": "UNCONFIRMED"},
    ]

    try:
        bugs = bapi.search_bugs(terms)["bugs"]
    except Exception as e:
        logger.warning("Fatal: Bugzilla search query failed, will not auto-assign bugs: {}".format(e))
        return

    try:
        bugzilla.DotDict(bugs[-1])
        logger.debug("Found {} unassigned bug(s). Assigning work!".format(len(bugs)))
        for bug in bugs:
            # Is this a valid request bug?
            if bug.get("whiteboard").startswith("autoentry"):
                logger.debug("{} is not valid, skipping".format(bug.get("id")))
                continue
            # Next assignee in the list, rotate
            if not dry_run:
                assignee = assign_list.pop()
                assign_list.insert(0, assignee)
            else:
                # dry_run does not rotate
                assignee = assign_list[0]

            # Is this a CASA bug or manual RRA request?
            if bug.get("creator") == ccfg.get("bot_email"):
                # This bug is sync'ed from CASA, look for "Product Line" in first comment
                comments = bapi.get_comments(bug.get("id"))["bugs"][str(bug.get("id"))]["comments"]
                casa_comment = capi.parse_casa_comment(comments[0]["text"])
                product_line = casa_comment.get("product_line")
                # If it has "Product Line: Firefox" then this should be assigned to FoxSec
                if "firefox" in product_line.lower():
                    # This is a Firefox-related project/vendor, should be handled by FoxSec
                    assignee = fcfg.get("assignees")[0]
            # RRA requested manually in Bugzilla
            else:
                comment_0 = bapi.get_comments(bug.get("id"))["bugs"][str(bug.get("id"))]["comments"][0]["text"]
                if any(keyword in comment_0.lower() for keyword in foxsec_keywords):
                    # This is a Firefox-related project/vendor, should be handled by FoxSec
                    assignee = fcfg.get("assignees")[0]

            bug_up = bugzilla.DotDict()
            bug_up.assigned_to = assignee
            bug_up.status = "ASSIGNED"
            try:
                if not dry_run:
                    logger.info("Updating bug {} assigning {}".format(bug.get("id"), assignee))
                    bapi.put_bug(bug.get("id"), bug_up)
                else:
                    logger.info(
                        "Dry run, action not performed: would update bug {} assigning {}".format(
                            bug.get("id"), assignee
                        )
                    )
            except Exception as e:
                logging.debug("Failed to update bug {}: {}".format(bug.get("id"), e))

    except IndexError:
        logger.info("No unassigned bugs for component")

    with open(bcfg.get("cache"), "wb") as f:
        pickle.dump((assign_list, assign_hash), f)
Example #5
0
def autoassign(bapi, cfg, dry_run):
    """
    This will search through unassigned bugs and assign them automatically.
    @cfg: bugzilla configuration dict
    """
    global logger

    reset_assignees = False  # Controls if we're going to rewrite the cache that record who's the next assignee or not
    try:
        with open(cfg.get('cache'), 'rb') as f:
            (assign_list, assign_hash) = pickle.load(f)
            if set(assign_list) != set(cfg.get('assignees')):
                logger.info("List of assignees changed, resetting list!")
                reset_assignees = True
    except FileNotFoundError:
        reset_assignees = True

    if reset_assignees:
        assign_hash = cfg.get('assignees')
        assign_list = assign_hash[:]
        logger.info(
            "Configuring defaults for the NEW assignment list: {}".format(
                assign_hash))

    # Do we have any bugs in the queue?
    terms = [{
        'product': cfg.get('product')
    }, {
        'component': cfg.get('component')
    }, {
        'status': 'NEW'
    }, {
        'status': 'UNCONFIRMED'
    }]

    try:
        bugs = bapi.search_bugs(terms)['bugs']
    except Exception as e:
        logger.warning(
            'Fatal: Bugzilla search query failed, will not auto-assign bugs: {}'
            .format(e))
        return

    try:
        bugzilla.DotDict(bugs[-1])
        logger.debug("Found {} unassigned bug(s). Assigning work!".format(
            len(bugs)))
        for bug in bugs:
            # Is this a valid request bug?
            if bug.get('whiteboard').startswith('autoentry'):
                logger.debug("{} is not valid, skipping".format(bug.get('id')))
                continue
            # Next assignee in the list, rotate
            if not dry_run:
                assignee = assign_list.pop()
                assign_list.insert(0, assignee)
            else:
                # dry_run does not rotate
                assignee = assign_list[0]
            bug_up = bugzilla.DotDict()
            bug_up.assigned_to = assignee
            bug_up.status = 'ASSIGNED'
            try:
                if not dry_run:
                    logger.info("Updating bug {} assigning {}".format(
                        bug.get('id'), assignee))
                    bapi.put_bug(bug.get('id'), bug_up)
                else:
                    logger.info(
                        "Dry run, action not performed: would update bug {} assigning {}"
                        .format(bug.get('id'), assignee))
            except Exception as e:
                logging.debug("Failed to update bug {}: {}".format(
                    bug.get('id'), e))

    except IndexError:
        logger.info("No unassigned bugs for component")

    with open(cfg.get('cache'), 'wb') as f:
        pickle.dump((assign_list, assign_hash), f)
Example #6
0
def fill_bug(config, nags, rrajsondoc):
    bcfg = config['bugzilla']

    # If no API key has been specified, just skip this
    if len(bcfg['api_key']) == 0:
        return

    b = bugzilla.Bugzilla(url=bcfg['url'], api_key=bcfg['api_key'])

    #Did we already report this?
    terms = [{
        'product': bcfg['product']
    }, {
        'component': bcfg['component']
    }, {
        'creator': bcfg['creator']
    }, {
        'whiteboard': 'autoentry'
    }, {
        'resolution': ''
    }, {
        'status': 'NEW'
    }, {
        'status': 'ASSIGNED'
    }, {
        'status': 'REOPENED'
    }, {
        'status': 'UNCONFIRMED'
    }, {
        'whiteboard': 'rra2json={}'.format(rrajsondoc.source)
    }]

    bugs = b.search_bugs(terms)['bugs']
    try:
        bugzilla.DotDict(bugs[-1])
        debug("bug for {} is already present, not re-filling".format(
            rrajsondoc.source))
        return
    except IndexError:
        pass

    #If not, report now
    bug = bugzilla.DotDict()
    bug.product = bcfg['product']
    bug.component = bcfg['component']
    bug.summary = "There are {} issues with an RRA".format(len(nags))
    bug.description = json.dumps(nags)
    bug.whiteboard = 'autoentry rra2json={}'.format(rrajsondoc.source)
    if 'analyst' in rrajsondoc.details.metadata:
        bug.assigned_to = rrajsondoc.details.metadata.analyst
    try:
        ret = b.post_bug(bug)
        debug("Filled bug {} {}".format(rrajsondoc.source, ret))
    except Exception as e:
        # Code 51 = assigned_to user does not exist, just assign to default then
        url, estr, ecode, edict = e.args
        if edict['code'] == 51:
            del bug.assigned_to
            try:
                ret = b.post_bug(bug)
                debug("Filled bug {} {}".format(rrajsondoc.source, ret))
            except Exception as e1:
                debug("Filling bug failed: {}".format(e1))
        else:
            debug("Filling bug failed: {}".format(e))