Esempio n. 1
0
def update_with_git(ctxt, method="merge", user=None):
    subscription = asyncio.run(
        sub_utils.get_subscription(ctxt.client.auth.owner_id))

    creds = dict((login.lower(), token)
                 for login, token in subscription["tokens"].items())
    if user:
        token = creds.get(user.lower())
        if token:
            creds = {user: token}
        else:
            raise BranchUpdateFailure(
                f"Unable to rebase: user `{user}` is unknown. "
                f"Please make sure `{user}` has logged in Mergify dashboard.")

    for login, token in creds.items():
        try:
            return _do_update(ctxt, token, method)
        except AuthentificationFailure as e:  # pragma: no cover
            ctxt.log.info(
                "authentification failure, will retry another token: %s",
                e,
                login=login,
            )

    ctxt.log.warning("unable to update branch: no tokens are valid")

    if ctxt.pull_from_fork and ctxt.pull["base"]["repo"]["private"]:
        raise BranchUpdateFailure(
            "Rebasing a branch for a forked private repository is not supported by GitHub"
        )

    raise AuthentificationFailure("No valid OAuth tokens")
Esempio n. 2
0
def job_marketplace(event_type, event_id, data):

    owner = data["marketplace_purchase"]["account"]["login"]
    account_type = data["marketplace_purchase"]["account"]["type"]
    try:
        installation = github_app.get_client().get_installation(
            owner, account_type=account_type
        )
    except exceptions.MergifyNotInstalled:
        return

    r = utils.get_redis_for_cache()
    r.delete("subscription-cache-%s" % installation["id"])
    subscription = sub_utils.get_subscription(r, installation["id"])

    LOG.info(
        "Marketplace event",
        event_type=event_type,
        event_id=event_id,
        install_id=installation["id"],
        sender=data["sender"]["login"],
        gh_owner=owner,
        subscription_active=subscription["subscription_active"],
        subscription_reason=subscription["subscription_reason"],
    )
Esempio n. 3
0
def update_with_git(ctxt, method="merge"):
    redis = utils.get_redis_for_cache()

    subscription = sub_utils.get_subscription(redis,
                                              ctxt.client.installation["id"])

    for login, token in subscription["tokens"].items():
        try:
            return _do_update(ctxt, token, method)
        except AuthentificationFailure as e:  # pragma: no cover
            ctxt.log.info(
                "authentification failure, will retry another token: %s",
                e,
                login=login,
            )

    ctxt.log.warning("unable to update branch: no tokens are valid")

    if (ctxt.pull["head"]["repo"]["owner"]["login"] !=
            ctxt.pull["base"]["repo"]["owner"]["login"]
            and ctxt.pull["base"]["repo"]["private"]):
        raise BranchUpdateFailure(
            "Rebasing a branch for a forked private repository is not supported by GitHub"
        )

    raise AuthentificationFailure("No valid OAuth tokens")
Esempio n. 4
0
def run_engine(installation, owner, repo, pull_number, sources):
    logger = logs.getLogger(__name__,
                            gh_repo=repo,
                            gh_owner=owner,
                            gh_pull=pull_number)
    logger.debug("engine in thread start")
    try:
        sync_redis = utils.get_redis_for_cache()
        subscription = sub_utils.get_subscription(sync_redis,
                                                  installation["id"])
        logger.debug("engine get installation")
        with github.get_client(owner, repo, installation) as client:
            try:
                pull = client.item(f"pulls/{pull_number}")
            except httpx.HTTPClientSideError as e:
                if e.status_code == 404:
                    logger.debug("pull request doesn't exists, skipping it")
                    return
                raise

            if (pull["base"]["repo"]["private"]
                    and not subscription["subscription_active"]):
                logger.debug(
                    "pull request on private private repository without subscription, skipping it"
                )
                return

            engine.run(client, pull, sources)
    finally:
        logger.debug("engine in thread end")
Esempio n. 5
0
def job_marketplace(event_type, event_id, data):

    owner = data["marketplace_purchase"]["account"]["login"]
    account_type = data["marketplace_purchase"]["account"]["type"]
    integration = github.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY)
    try:
        installation_id = utils.get_installation_id(
            integration, owner, account_type=account_type
        )
    except github.GithubException as e:
        LOG.warning("%s: mergify not installed", owner, error=str(e))
        installation_id = None
        subscription = {
            "subscription_active": "Unknown",
            "subscription_reason": "No",
            "tokens": None,
        }
    else:
        r = utils.get_redis_for_cache()
        r.delete("subscription-cache-%s" % installation_id)
        subscription = sub_utils.get_subscription(r, installation_id)

    LOG.info(
        "Marketplace event",
        event_type=event_type,
        event_id=event_id,
        install_id=installation_id,
        sender=data["sender"]["login"],
        subscription_active=subscription["subscription_active"],
        subscription_reason=subscription["subscription_reason"],
    )
Esempio n. 6
0
def job_marketplace(event_type, event_id, data):

    owner = data["marketplace_purchase"]["account"]["login"]
    account_type = data["marketplace_purchase"]["account"]["type"]
    try:
        installation_id = github_app.get_client().get_installation_id(
            owner, account_type=account_type
        )
    except httpx.HTTPClientSideError as e:
        LOG.warning("mergify not installed", gh_owner=owner, error=str(e))
        installation_id = None
        subscription = {
            "subscription_active": "Unknown",
            "subscription_reason": "No",
            "tokens": None,
        }
    else:
        r = utils.get_redis_for_cache()
        r.delete("subscription-cache-%s" % installation_id)
        subscription = sub_utils.get_subscription(r, installation_id)

    LOG.info(
        "Marketplace event",
        event_type=event_type,
        event_id=event_id,
        install_id=installation_id,
        sender=data["sender"]["login"],
        gh_owner=owner,
        subscription_active=subscription["subscription_active"],
        subscription_reason=subscription["subscription_reason"],
    )
Esempio n. 7
0
    def process(self):
        pull_numbers = self.get_pulls()

        self.log.info("%d pulls queued", len(pull_numbers), queue=list(pull_numbers))

        if not pull_numbers:
            return

        pull_number = int(pull_numbers[0])

        try:
            installation = github.get_installation(
                self.owner, self.repo, self.installation_id
            )
        except exceptions.MergifyNotInstalled:
            self.delete_queue()
            return

        subscription = sub_utils.get_subscription(
            utils.get_redis_for_cache(), self.installation_id,
        )

        with github.get_client(self.owner, self.repo, installation) as client:
            data = client.item(f"pulls/{pull_number}")

            try:
                ctxt = context.Context(client, data, subscription)
            except exceptions.RateLimited as e:
                self.log.debug("rate limited", remaining_seconds=e.countdown)
                return
            except exceptions.MergeableStateUnknown as e:  # pragma: no cover
                e.ctxt.log.warning(
                    "pull request with mergeable_state unknown retrying later",
                )
                self._move_pull_at_end(pull_number)
                return
            try:
                if ctxt.pull["base"]["ref"] != self.ref:
                    ctxt.log.info(
                        "pull request base branch have changed",
                        old_branch=self.ref,
                        new_branch=ctxt.pull["base"]["ref"],
                    )
                    self._move_pull_to_new_base_branch(ctxt, self.ref)
                elif ctxt.pull["state"] == "closed" or ctxt.is_behind:
                    # NOTE(sileht): Pick up this pull request and rebase it again
                    # or update its status and remove it from the queue
                    ctxt.log.info(
                        "pull request needs to be updated again or has been closed",
                    )
                    self.handle_first_pull_in_queue(ctxt)
                else:
                    # NOTE(sileht): Pull request has not been merged or cancelled
                    # yet wait next loop
                    ctxt.log.info("pull request checks are still in progress")
            except Exception:  # pragma: no cover
                ctxt.log.error("Fail to process merge queue", exc_info=True)
                self._move_pull_at_end(pull_number)
Esempio n. 8
0
    def process(self):
        pull_numbers = self.get_pulls()

        self.log.info("%d pulls queued", len(pull_numbers), queue=list(pull_numbers))

        if not pull_numbers:
            return

        pull_number = pull_numbers[0]

        with github.get_client(self.owner, self.repo) as client:
            ctxt = None
            try:
                subscription = asyncio.run(
                    sub_utils.get_subscription(client.auth.owner_id)
                )
                data = client.item(f"pulls/{pull_number}")

                ctxt = context.Context(client, data, subscription)
                if ctxt.pull["base"]["ref"] != self.ref:
                    ctxt.log.info(
                        "pull request base branch have changed",
                        old_branch=self.ref,
                        new_branch=ctxt.pull["base"]["ref"],
                    )
                    self._move_pull_to_new_base_branch(ctxt.pull["number"], self.ref)
                elif ctxt.pull["state"] == "closed" or ctxt.is_behind:
                    # NOTE(sileht): Pick up this pull request and rebase it again
                    # or update its status and remove it from the queue
                    ctxt.log.info(
                        "pull request needs to be updated again or has been closed",
                    )
                    self.handle_first_pull_in_queue(ctxt)
                else:
                    # NOTE(sileht): Pull request has not been merged or cancelled
                    # yet wait next loop
                    ctxt.log.info("pull request checks are still in progress")

            except Exception as exc:  # pragma: no cover
                log = self.log if ctxt is None else ctxt.log

                if exceptions.should_be_ignored(exc):
                    log.info(
                        "Fail to process merge queue, remove the pull request from the queue",
                        exc_info=True,
                    )
                    self.remove_pull(ctxt.pull["number"])

                elif exceptions.need_retry(exc):
                    log.info("Fail to process merge queue, need retry", exc_info=True)
                    if isinstance(exc, exceptions.MergeableStateUnknown):
                        # NOTE(sileht): We need GitHub to recompute the state here (by
                        # merging something else for example), so move it to the end
                        self._move_pull_at_end(pull_number)

                else:
                    log.error("Fail to process merge queue", exc_info=True)
                    self._move_pull_at_end(pull_number)
Esempio n. 9
0
def job_refresh(owner, repo, refresh_ref):
    LOG.info("%s/%s/%s: refreshing", owner, repo, refresh_ref)

    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)
    try:
        installation_id = utils.get_installation_id(integration, owner, repo)
    except github.GithubException as e:
        LOG.warning("%s/%s/%s: mergify not installed",
                    owner, repo, refresh_ref, error=str(e))
        return

    token = integration.get_access_token(installation_id).token
    g = github.Github(token, base_url="https://api.%s" % config.GITHUB_DOMAIN)
    r = g.get_repo("%s/%s" % (owner, repo))

    if refresh_ref == "full" or refresh_ref.startswith("branch/"):
        if refresh_ref.startswith("branch/"):
            branch = refresh_ref[7:]
            pulls = r.get_pulls(base=branch)
        else:
            branch = '*'
            pulls = r.get_pulls()
        key = "queues~%s~%s~%s~%s~%s" % (installation_id, owner.lower(),
                                         repo.lower(), r.private, branch)
        utils.get_redis_for_cache().delete(key)
    else:
        try:
            pull_number = int(refresh_ref[5:])
        except ValueError:  # pragma: no cover
            LOG.info("%s/%s/%s: Invalid PR ref", owner, repo, refresh_ref)
            return
        pulls = [r.get_pull(pull_number)]

    subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                              installation_id)

    if r.archived:  # pragma: no cover
        LOG.warning("%s/%s/%s: repository archived",
                    owner, repo, refresh_ref)
        return

    if not subscription["tokens"]:  # pragma: no cover
        LOG.warning("%s/%s/%s: installation without token",
                    owner, repo, refresh_ref)
        return

    for p in pulls:
        # Mimic the github event format
        data = {
            'repository': r.raw_data,
            'installation': {'id': installation_id},
            'pull_request': p.raw_data,
        }
        engine.run.s('refresh', data).apply_async()
Esempio n. 10
0
def job_filter_and_dispatch(event_type, event_id, data):
    meter_event(event_type, data)

    if "installation" in data:
        installation_id = data["installation"]["id"]
        installation_owner = data["installation"].get("account", {"login": "******"})[
            "login"
        ]
        subscription = sub_utils.get_subscription(
            utils.get_redis_for_cache(), installation_id
        )
    else:
        installation_id = "Unknown"
        installation_owner = "Unknown"
        subscription = {
            "subscription_active": "Unknown",
            "subscription_reason": "No",
            "tokens": None,
        }

    reason = get_ignore_reason(subscription, event_type, data)
    if reason:
        msg_action = reason
    elif event_type in ["push"]:
        owner, _, repo = data["repository"]["full_name"].partition("/")
        branch = data["ref"][11:]
        msg_action = "run refresh branch %s" % branch
        mergify_events.job_refresh.s(owner, repo, "branch", branch).apply_async(
            countdown=10
        )
    else:
        engine.run.s(event_type, data).apply_async()
        msg_action = "pushed to backend%s" % get_extra_msg_from_event(event_type, data)

    if "repository" in data:
        repo_name = data["repository"]["full_name"]
        private = data["repository"]["private"]
    else:
        repo_name = "Unknown"
        private = "Unknown"

    LOG.info(
        "GithubApp event %s",
        msg_action,
        event_type=event_type,
        event_id=event_id,
        install_id=installation_id,
        install_owner=installation_owner,
        sender=data["sender"]["login"],
        repository=repo_name,
        private=private,
        subscription_active=subscription["subscription_active"],
        subscription_reason=subscription["subscription_reason"],
    )
Esempio n. 11
0
def update(pull, installation_id, method="merge"):
    redis = utils.get_redis_for_cache()

    subscription = sub_utils.get_subscription(redis, installation_id)

    for login, token in subscription["tokens"].items():
        try:
            return _do_update(pull, token, method)
        except AuthentificationFailure as e:  # pragma: no cover
            LOG.debug("authentification failure, will retry another token: %s",
                      e,
                      login=login,
                      pull_request=pull)

    LOG.error("unable to update branch: no tokens are valid",
              pull_request=pull)
Esempio n. 12
0
def update_with_git(pull, method="merge"):
    redis = utils.get_redis_for_cache()

    subscription = sub_utils.get_subscription(redis, pull.installation_id)

    for login, token in subscription["tokens"].items():
        try:
            return _do_update(pull, token, method)
        except AuthentificationFailure as e:  # pragma: no cover
            pull.log.debug(
                "authentification failure, will retry another token: %s",
                e,
                login=login,
            )

    pull.log.error("unable to update branch: no tokens are valid")
    raise BranchUpdateFailure("No oauth valid tokens")
Esempio n. 13
0
def job_refresh_all():
    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)

    counts = [0, 0, 0]
    for install in utils.get_installations(integration):
        counts[0] += 1
        token = integration.get_access_token(install["id"]).token
        g = github.Github(token,
                          base_url="https://api.%s" % config.GITHUB_DOMAIN)
        i = g.get_installation(install["id"])

        subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                                  install["id"])
        if not subscription["token"]:  # pragma: no cover
            continue

        for r in i.get_repos():
            if r.archived:  # pragma: no cover
                continue
            if r.private and not subscription["subscribed"]:
                continue
            try:
                r.get_contents(".mergify.yml")
            except github.GithubException as e:  # pragma: no cover
                if e.status == 404:
                    continue
                else:
                    raise

            counts[1] += 1
            for p in list(r.get_pulls()):
                # Mimic the github event format
                data = {
                    'repository': r.raw_data,
                    'installation': {
                        'id': install["id"]
                    },
                    'pull_request': p.raw_data,
                }
                engine.run.s('refresh', data).apply_async()

    LOG.info("Refreshing %s installations, %s repositories, "
             "%s branches", *counts)
Esempio n. 14
0
def job_marketplace(event_type, event_id, data):

    owner = data["marketplace_purchase"]["account"]["login"]
    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)
    installation_id = utils.get_installation_id(integration, owner)

    r = utils.get_redis_for_cache()
    r.delete("subscription-cache-%s" % installation_id)

    subscription = sub_utils.get_subscription(
        utils.get_redis_for_cache(), installation_id)

    LOG.info('Marketplace event',
             event_type=event_type,
             event_id=event_id,
             install_id=installation_id,
             sender=data["sender"]["login"],
             subscription_active=subscription["subscription_active"],
             subscription_reason=subscription["subscription_reason"])
Esempio n. 15
0
def report(url):
    redis = utils.get_redis_for_cache()
    path = url.replace("https://github.com/", "")
    try:
        owner, repo, _, pull_number = path.split("/")
    except ValueError:
        print(f"Wrong URL: {url}")
        return
    slug = owner + "/" + repo

    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)
    install_id = utils.get_installation_id(integration, owner, repo=repo)

    print("* INSTALLATION ID: %s" % install_id)

    cached_sub = sub_utils.get_subscription(redis, install_id)
    db_sub = sub_utils._retrieve_subscription_from_db(install_id)
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    report_sub(install_id, slug, cached_sub, "ENGINE-CACHE")
    report_sub(install_id, slug, db_sub, "DASHBOARD")

    installation_token = integration.get_access_token(install_id).token

    g = github.Github(installation_token,
                      base_url="https://api.%s" % config.GITHUB_DOMAIN)
    r = g.get_repo(owner + "/" + repo)
    print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC")

    print("* CONFIGURATION:")
    try:
        mergify_config_content = rules.get_mergify_config_content(r)
    except rules.NoRules:  # pragma: no cover
        print(".mergify.yml is missing")
    else:
        print(mergify_config_content.decode())

    try:
        mergify_config = rules.UserConfigurationSchema(mergify_config_content)
    except rules.InvalidRules as e:  # pragma: no cover
        print("configuration is invalid %s" % str(e))
    else:
        pull_request_rules_raw = mergify_config["pull_request_rules"].as_dict()
        pull_request_rules_raw["rules"].extend(
            actions_runner.MERGIFY_RULE["rules"])
        pull_request_rules = rules.PullRequestRules(**pull_request_rules_raw)

    try:
        p = r.get_pull(int(pull_number))
    except github.UnknownObjectException:
        print("Wrong pull request number")
        return g, None

    mp = mergify_pull.MergifyPull(g, p, install_id)
    print("* PULL REQUEST:")
    pprint.pprint(mp.to_dict(), width=160)
    try:
        print("is_behind: %s" % mp.is_behind())
    except github.GithubException as e:
        print("Unable to know if pull request branch is behind: %s" % e)

    print("mergeable_state: %s" % mp.g_pull.mergeable_state)

    print("* MERGIFY LAST CHECKS:")
    checks = list(check_api.get_checks(p))
    for c in checks:
        if c._rawData["app"]["id"] == config.INTEGRATION_ID:
            print("[%s]: %s | %s" %
                  (c.name, c.conclusion, c.output.get("title")))
            print("> " + "\n> ".join(c.output.get("summary").split("\n")))

    print("* MERGIFY LIVE MATCHES:")
    match = pull_request_rules.get_pull_request_rule(mp)
    summary_title, summary = actions_runner.gen_summary(
        "refresh", {}, mp, match)
    print("> %s" % summary_title)
    print(summary)

    return g, p
Esempio n. 16
0
def update(pull, installation_id, method="merge"):
    # NOTE(sileht):
    # $ curl https://api.github.com/repos/sileht/repotest/pulls/2 | jq .commits
    # 2
    # $ git clone https://[email protected]/sileht-tester/repotest \
    #           --depth=$((2 + 1)) -b sileht/testpr
    # $ cd repotest
    # $ git remote add upstream https://[email protected]/sileht/repotest.git
    # $ git log | grep Date | tail -1
    # Date:   Fri Mar 30 21:30:26 2018 (10 days ago)
    # $ git fetch upstream master --shallow-since="Fri Mar 30 21:30:26 2018"
    # $ git rebase upstream/master
    # $ git push origin sileht/testpr:sileht/testpr

    redis = utils.get_redis_for_cache()

    subscription = sub_utils.get_subscription(redis, installation_id)

    if not subscription:
        LOG.error("subscription to update branch is missing")
        return

    token = subscription["token"]

    head_repo = pull.g_pull.head.repo.full_name
    base_repo = pull.g_pull.base.repo.full_name

    head_branch = pull.g_pull.head.ref
    base_branch = pull.g_pull.base.ref
    git = utils.Gitter()
    try:
        git("init")
        git.configure()
        git.add_cred(token, "", head_repo)
        git.add_cred(token, "", base_repo)
        git("remote", "add", "origin",
            "https://%s/%s" % (config.GITHUB_DOMAIN, head_repo))
        git("remote", "add", "upstream",
            "https://%s/%s" % (config.GITHUB_DOMAIN, base_repo))

        depth = int(pull.g_pull.commits) + 1
        git("fetch", "--quiet", "--depth=%d" % depth, "origin", head_branch)
        git("checkout", "-q", "-b", head_branch, "origin/%s" % head_branch)

        out = git("log", "--format=%cI")
        last_commit_date = [
            d for d in out.decode("utf8").split("\n") if d.strip()
        ][-1]

        git("fetch", "--quiet", "upstream", base_branch,
            "--shallow-since='%s'" % last_commit_date)

        try:
            _do_update(git, method, base_branch, head_branch)
        except subprocess.CalledProcessError as e:
            if b"unrelated histories" in e.output:
                LOG.debug("Complete history cloned", pull_request=pull)
                # NOTE(sileht): We currently assume we have only one parent
                # commit in common. Since Git is a graph, in some case this
                # graph can be more complicated.
                # So, retrying with the whole git history for now
                git("fetch", "--quiet", "origin", head_branch)
                git("fetch", "--quiet", "upstream", base_branch)
                _do_update(git, method, base_branch, head_branch)
            else:
                raise

        return git("log", "-1", "--format=%H").decode().strip()
    except Exception:  # pragma: no cover
        LOG.error("update branch fail", pull_request=pull, exc_info=True)
    finally:
        git.cleanup()
Esempio n. 17
0
def run(event_type, data):
    """Everything starts here."""
    installation_id = data["installation"]["id"]
    owner = data["repository"]["owner"]["login"]
    repo = data["repository"]["name"]

    client = github.get_client(owner, repo, installation_id)

    raw_pull = get_github_pull_from_event(client, event_type, data)

    if not raw_pull:  # pragma: no cover
        LOG.info(
            "No pull request found in the event %s, ignoring",
            event_type,
            gh_owner=owner,
            gh_repo=repo,
        )
        return

    pull = mergify_pull.MergifyPull(client, raw_pull)
    # Override pull_request with the updated one
    data["pull_request"] = pull.data

    pull.log.info("Pull request found in the event %s", event_type)

    if ("base" not in pull.data or "repo" not in pull.data["base"]
            or len(list(pull.data["base"]["repo"].keys())) < 70):
        pull.log.warning(
            "the pull request payload looks suspicious",
            event_type=event_type,
            data=data,
        )

    if (event_type == "status"
            and pull.data["head"]["sha"] != data["sha"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got status of an old commit)", )
        return

    elif (event_type in ["status", "check_suite", "check_run"]
          and pull.data["merged"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got status of a merged pull request)", )
        return
    elif (event_type in ["check_suite", "check_run"]
          and pull.data["head"]["sha"] !=
          data[event_type]["head_sha"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got %s of an old "
            "commit)",
            event_type,
        )
        return

    if check_configuration_changes(pull.g_pull):
        pull.log.info("Configuration changed, ignoring", )
        return

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = rules.get_mergify_config(pull.g_pull.base.repo)
    except rules.NoRules:  # pragma: no cover
        pull.log.info("No need to proceed queue (.mergify.yml is missing)", )
        return
    except rules.InvalidRules as e:  # pragma: no cover
        # Not configured, post status check with the error message
        if event_type == "pull_request" and data["action"] in [
                "opened", "synchronize"
        ]:
            check_api.set_check_run(
                pull.g_pull,
                "Summary",
                "completed",
                "failure",
                output={
                    "title": "The Mergify configuration is invalid",
                    "summary": str(e),
                },
            )
        return

    # Add global and mandatory rules
    mergify_config["pull_request_rules"].rules.extend(
        rules.load_pull_request_rules_schema(MERGIFY_RULE["rules"]))

    subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                              installation_id)

    if pull.data["base"]["repo"][
            "private"] and not subscription["subscription_active"]:
        check_api.set_check_run(
            pull.g_pull,
            "Summary",
            "completed",
            "failure",
            output={
                "title": "Mergify is disabled",
                "summary": subscription["subscription_reason"],
            },
        )
        return

    # CheckRun are attached to head sha, so when user add commits or force push
    # we can't directly get the previous Mergify Summary. So we copy it here, then
    # anything that looks at it in next celery tasks will find it.
    if event_type == "pull_request" and data["action"] == "synchronize":
        copy_summary_from_previous_head_sha(pull.g_pull, data["before"])

    sources = [{"event_type": event_type, "data": data}]

    commands_runner.spawn_pending_commands_tasks(pull, sources)

    if event_type == "issue_comment":
        commands_runner.run_command(pull, sources, data["comment"]["body"],
                                    data["comment"]["user"])
    else:
        actions_runner.handle(mergify_config["pull_request_rules"], pull,
                              sources)
Esempio n. 18
0
def report(url):
    redis = utils.get_redis_for_cache()
    path = url.replace("https://github.com/", "")
    try:
        owner, repo, _, pull_number = path.split("/")
    except ValueError:
        print(f"Wrong URL: {url}")
        return
    slug = owner + "/" + repo

    try:
        installation = github.get_installation(owner, repo)
    except exceptions.MergifyNotInstalled:
        print("* Mergify is not installed there")
        return

    client = github.get_client(owner, repo, installation)

    print("* INSTALLATION ID: %s" % client.installation["id"])

    cached_sub = sub_utils.get_subscription(redis, client.installation["id"])
    db_sub = sub_utils._retrieve_subscription_from_db(
        client.installation["id"])
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    report_sub(client.installation["id"], slug, cached_sub, "ENGINE-CACHE")
    report_sub(client.installation["id"], slug, db_sub, "DASHBOARD")

    pull_raw = client.item(f"pulls/{pull_number}")
    ctxt = mergify_context.MergifyContext(client, pull_raw)

    print("* REPOSITORY IS %s" %
          "PRIVATE" if ctxt.pull["base"]["repo"]["private"] else "PUBLIC")

    print("* CONFIGURATION:")
    try:
        mergify_config_content = rules.get_mergify_config_content(ctxt)
    except rules.NoRules:  # pragma: no cover
        print(".mergify.yml is missing")
        pull_request_rules = None
    else:
        print(mergify_config_content.decode())
        try:
            mergify_config = rules.UserConfigurationSchema(
                mergify_config_content)
        except rules.InvalidRules as e:  # pragma: no cover
            print("configuration is invalid %s" % str(e))
        else:
            pull_request_rules_raw = mergify_config[
                "pull_request_rules"].as_dict()
            pull_request_rules_raw["rules"].extend(
                engine.MERGIFY_RULE["rules"])
            pull_request_rules = rules.PullRequestRules(
                **pull_request_rules_raw)

    print("* PULL REQUEST:")
    pprint.pprint(ctxt.to_dict(), width=160)

    print("is_behind: %s" % ctxt.is_behind)

    print("mergeable_state: %s" % ctxt.pull["mergeable_state"])

    print("* MERGIFY LAST CHECKS:")
    checks = list(check_api.get_checks(ctxt, mergify_only=True))
    for c in checks:
        print("[%s]: %s | %s" %
              (c["name"], c["conclusion"], c["output"].get("title")))
        print("> " + "\n> ".join(c["output"].get("summary").split("\n")))

    if pull_request_rules is not None:
        print("* MERGIFY LIVE MATCHES:")
        match = pull_request_rules.get_pull_request_rule(ctxt)
        summary_title, summary = actions_runner.gen_summary(
            ctxt, [{
                "event_type": "refresh",
                "data": {}
            }], match)
        print("> %s" % summary_title)
        print(summary)

    return ctxt
Esempio n. 19
0
    def handle_installation(installation):
        try:
            _id = installation["id"]
            target_type = installation["target_type"]
            account = installation["account"]["login"]

            LOG.info("Get subscription", account=account)
            subs = sub_utils.get_subscription(redis, _id)
            subscribed = subs["subscription_active"]

            costs[(subscribed, target_type, account)] = (
                subs["subscription_cost"]
            )

            with installations_lock:
                installations[(subscribed, target_type)] += 1

            token = integration.get_access_token(_id).token
            g = github.Github(token, base_url="https://api.%s" %
                              config.GITHUB_DOMAIN)

            if installation["target_type"] == "Organization":
                LOG.info("Get members",
                         install=installation["account"]["login"])
                org = g.get_organization(installation["account"]["login"])
                value = len(list(org.get_members()))

                users_per_installation[
                    (subscribed, target_type, account)] = value
            else:
                users_per_installation[
                    (subscribed, target_type, account)] = 1

            LOG.info("Get repos", account=account)

            repositories = sorted(g.get_installation(_id).get_repos(),
                                  key=operator.attrgetter("private"))
            for private, repos in itertools.groupby(
                    repositories, key=operator.attrgetter("private")):

                configured_repos = 0
                unconfigured_repos = 0
                for repo in repos:
                    try:
                        rules.get_mergify_config(repo)
                        configured_repos += 1
                        redis.sadd("badges.tmp", repo.full_name)
                    except github.GithubException as e:
                        if e.status >= 500:  # pragma: no cover
                            raise
                        unconfigured_repos += 1
                    except (rules.InvalidRules, rules.NoRules):
                        unconfigured_repos += 1

                repositories_per_installation[
                    (subscribed, target_type, account, private, True)
                ] = configured_repos
                repositories_per_installation[
                    (subscribed, target_type, account, private, False)
                ] = unconfigured_repos
        except github.GithubException as e:  # pragma: no cover
            # Ignore rate limit/abuse, authorization issues
            # and GitHub malfunction
            if e.status not in (403, 401) and e.status < 500:
                raise
Esempio n. 20
0
def run(client, pull, sources):
    LOG.debug("engine get sub")
    subscription = sub_utils.get_subscription(
        utils.get_redis_for_cache(), client.installation["id"]
    )

    LOG.debug("engine get context")
    ctxt = context.Context(client, pull, subscription)
    ctxt.log.debug("engine start processing context")

    issue_comment_sources = []

    for source in sources:
        if source["event_type"] == "issue_comment":
            issue_comment_sources.append(source)
        else:
            ctxt.sources.append(source)

    ctxt.log.debug("engine run pending commands")
    commands_runner.run_pending_commands_tasks(ctxt)

    if issue_comment_sources:
        ctxt.log.debug("engine handle commands")
        for source in issue_comment_sources:
            commands_runner.handle(
                ctxt,
                source["data"]["comment"]["body"],
                source["data"]["comment"]["user"],
            )

    if not ctxt.sources:
        return

    if ctxt.client.installation["permissions_need_to_be_updated"]:
        check_api.set_check_run(
            ctxt,
            "Summary",
            "completed",
            "failure",
            output={
                "title": "Required GitHub permissions are missing.",
                "summary": "You can accept them at https://dashboard.mergify.io/",
            },
        )
        return

    ctxt.log.debug("engine check configuration change")
    if check_configuration_changes(ctxt):
        ctxt.log.info("Configuration changed, ignoring")
        return

    ctxt.log.debug("engine get configuration")
    # BRANCH CONFIGURATION CHECKING
    try:
        filename, mergify_config = rules.get_mergify_config(ctxt)
    except rules.NoRules:  # pragma: no cover
        ctxt.log.info("No need to proceed queue (.mergify.yml is missing)")
        return
    except rules.InvalidRules as e:  # pragma: no cover
        # Not configured, post status check with the error message
        if any(
            (
                s["event_type"] == "pull_request"
                and s["data"]["action"] in ["opened", "synchronize"]
                for s in ctxt.sources
            )
        ):
            check_api.set_check_run(
                ctxt,
                actions_runner.SUMMARY_NAME,
                "completed",
                "failure",
                output={
                    "title": "The Mergify configuration is invalid",
                    "summary": str(e),
                    "annotations": e.get_annotations(e.filename),
                },
            )
        return

    # Add global and mandatory rules
    mergify_config["pull_request_rules"].rules.extend(
        rules.PullRequestRules.from_list(MERGIFY_RULE["rules"]).rules
    )

    if ctxt.pull["base"]["repo"]["private"] and not subscription["subscription_active"]:
        check_api.set_check_run(
            ctxt,
            actions_runner.SUMMARY_NAME,
            "completed",
            "failure",
            output={
                "title": "Mergify is disabled",
                "summary": subscription["subscription_reason"],
            },
        )
        return

    # CheckRun are attached to head sha, so when user add commits or force push
    # we can't directly get the previous Mergify Summary. So we copy it here, then
    # anything that looks at it in next celery tasks will find it.

    synchronize_data = [
        s["data"]
        for s in ctxt.sources
        if s["event_type"] == "pull_request"
        and s["data"]["action"] == "synchronize"
        and s["data"]["after"] == ctxt.pull["head"]["sha"]
    ]
    if synchronize_data:
        ctxt.log.debug("engine synchronize summary")
        copy_summary_from_previous_head_sha(ctxt, synchronize_data[0]["before"])

    ctxt.log.debug("engine handle actions")
    actions_runner.handle(mergify_config["pull_request_rules"], ctxt)
Esempio n. 21
0
def update(pull, installation_id, method="merge"):
    # NOTE(sileht):
    # $ curl https://api.github.com/repos/sileht/repotest/pulls/2 | jq .commits
    # 2
    # $ git clone https://[email protected]/sileht-tester/repotest \
    #           --depth=$((2 + 1)) -b sileht/testpr
    # $ cd repotest
    # $ git remote add upstream https://[email protected]/sileht/repotest.git
    # $ git log | grep Date | tail -1
    # Date:   Fri Mar 30 21:30:26 2018 (10 days ago)
    # $ git fetch upstream master --shallow-since="Fri Mar 30 21:30:26 2018"
    # $ git rebase upstream/master
    # $ git push origin sileht/testpr:sileht/testpr

    redis = utils.get_redis_for_cache()

    subscription = sub_utils.get_subscription(redis, installation_id)

    if not subscription:
        LOG.error("subscription to update branch is missing")
        return

    token = subscription["token"]

    head_repo = pull.g_pull.head.repo.full_name
    base_repo = pull.g_pull.base.repo.full_name

    head_branch = pull.g_pull.head.ref
    base_branch = pull.g_pull.base.ref
    git = utils.Gitter()
    try:
        git("init")
        git.configure()
        git.add_cred(token, "", head_repo)
        git.add_cred(token, "", base_repo)
        git("remote", "add", "origin",
            "https://%s/%s" % (config.GITHUB_DOMAIN, head_repo))
        git("remote", "add", "upstream",
            "https://%s/%s" % (config.GITHUB_DOMAIN, base_repo))

        depth = int(pull.g_pull.commits) + 1
        git("fetch", "--quiet", "--depth=%d" % depth, "origin", head_branch)
        git("checkout", "-q", "-b", head_branch, "origin/%s" % head_branch)

        out = git("log", "--format=%cI")
        last_commit_date = [d for d in out.decode("utf8").split("\n")
                            if d.strip()][-1]

        git("fetch", "--quiet", "upstream", pull.g_pull.base.ref,
            "--shallow-since='%s'" % last_commit_date)

        if method == "merge":
            git("merge", "--quiet", "upstream/%s" % base_branch, "-m",
                "Merge branch '%s' into '%s'" % (base_branch, head_branch))
            git("push", "--quiet", "origin", head_branch)
        elif method == "rebase":
            git("rebase", "upstream/%s" % base_branch)
            git("push", "--quiet", "origin", head_branch, "-f")
        else:
            raise RuntimeError("Invalid branch update method")

        return git("log", "-1", "--format=%H").decode().strip()
    except Exception:  # pragma: no cover
        LOG.error("update branch fail", pull_request=pull, exc_info=True)
    finally:
        git.cleanup()
Esempio n. 22
0
def run(event_type, data):
    """Everything starts here."""
    installation_id = data["installation"]["id"]
    installation_token = utils.get_installation_token(installation_id)
    if not installation_token:
        return

    g = github.Github(installation_token,
                      base_url="https://api.%s" % config.GITHUB_DOMAIN)

    if config.LOG_RATELIMIT:  # pragma: no cover
        rate = g.get_rate_limit().rate
        LOG.info(
            "ratelimit: %s/%s, reset at %s",
            rate.remaining,
            rate.limit,
            rate.reset,
            repository=data["repository"]["name"],
        )

    try:
        repo = g.get_repo(data["repository"]["owner"]["login"] + "/" +
                          data["repository"]["name"])
    except github.UnknownObjectException:  # pragma: no cover
        LOG.info("Repository not found in the event %s, ignoring", event_type)
        return

    event_pull = get_github_pull_from_event(repo, event_type, data)

    if not event_pull:  # pragma: no cover
        LOG.info("No pull request found in the event %s, "
                 "ignoring", event_type)
        return

    # Override pull_request with the updated one
    data["pull_request"] = event_pull.raw_data

    LOG.info(
        "Pull request found in the event %s",
        event_type,
        repo=repo.full_name,
        pull_request=event_pull,
    )

    if ("base" not in event_pull.raw_data
            or "repo" not in event_pull.raw_data["base"]
            or len(list(event_pull.raw_data["base"]["repo"].keys())) < 70):
        LOG.warning(
            "the pull request payload looks suspicious",
            event_type=event_type,
            data=data,
            pull_request=event_pull.raw_data,
            repo=repo.fullname,
        )

    if (event_type == "status"
            and event_pull.head.sha != data["sha"]):  # pragma: no cover
        LOG.info(
            "No need to proceed queue (got status of an old commit)",
            repo=repo.full_name,
            pull_request=event_pull,
        )
        return

    elif (event_type in ["status", "check_suite", "check_run"]
          and event_pull.merged):  # pragma: no cover
        LOG.info(
            "No need to proceed queue (got status of a merged "
            "pull request)",
            repo=repo.full_name,
            pull_request=event_pull,
        )
        return
    elif (event_type in ["check_suite", "check_run"] and event_pull.head.sha !=
          data[event_type]["head_sha"]):  # pragma: no cover
        LOG.info(
            "No need to proceed queue (got %s of an old "
            "commit)",
            event_type,
            repo=repo.full_name,
            pull_request=event_pull,
        )
        return

    if check_configuration_changes(event_pull):
        LOG.info(
            "Configuration changed, ignoring",
            repo=repo.full_name,
            pull_request=event_pull,
        )
        return

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = rules.get_mergify_config(repo)
    except rules.NoRules:  # pragma: no cover
        LOG.info(
            "No need to proceed queue (.mergify.yml is missing)",
            repo=repo.full_name,
            pull_request=event_pull,
        )
        return
    except rules.InvalidRules as e:  # pragma: no cover
        # Not configured, post status check with the error message
        if event_type == "pull_request" and data["action"] in [
                "opened", "synchronize"
        ]:
            check_api.set_check_run(
                event_pull,
                "Summary",
                "completed",
                "failure",
                output={
                    "title": "The Mergify configuration is invalid",
                    "summary": str(e),
                },
            )
        return

    subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                              installation_id)

    if repo.private and not subscription["subscription_active"]:
        check_api.set_check_run(
            event_pull,
            "Summary",
            "completed",
            "failure",
            output={
                "title": "Mergify is disabled",
                "summary": subscription["subscription_reason"],
            },
        )
        return

    # CheckRun are attached to head sha, so when user add commits or force push
    # we can't directly get the previous Mergify Summary. So we copy it here, then
    # anything that looks at it in next celery tasks will find it.
    if event_type == "pull_request" and data["action"] == "synchronize":
        copy_summary_from_previous_head_sha(event_pull, data["before"])

    commands_runner.spawn_pending_commands_tasks(installation_id, event_type,
                                                 data, event_pull)

    if event_type == "issue_comment":
        commands_runner.run_command.s(installation_id, event_type, data,
                                      data["comment"]["body"]).apply_async()
    else:
        actions_runner.handle.s(
            installation_id,
            mergify_config["pull_request_rules"].as_dict(),
            event_type,
            data,
        ).apply_async()
Esempio n. 23
0
def report(url):
    redis = utils.get_redis_for_cache()
    path = url.replace("https://github.com/", "")
    owner, repo, _, pull_number = path.split("/")

    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)
    install_id = utils.get_installation_id(integration, owner, repo=repo)

    print("* INSTALLATION ID: %s" % install_id)

    cached_sub = sub_utils.get_subscription(redis, install_id)
    db_sub = sub_utils._retrieve_subscription_from_db(install_id)
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    print("* SUB DETAIL: %s" % db_sub["subscription_reason"])

    print("* NUMBER OF CACHED TOKENS: %d" % len(cached_sub["tokens"]))

    try:
        for login, token in cached_sub["tokens"].items():
            try:
                repos = get_repositories_setuped(token, install_id)
            except github.BadCredentialsException:
                print("** token for %s invalid" % login)
            except github.GithubException as e:
                if e.status != 401:
                    raise
                print("** token for %s invalid" % login)
            else:
                if any((r["full_name"] == owner + "/" + repo) for r in repos):
                    print("* MERGIFY INSTALLED AND ENABLED ON THIS REPOSITORY")
                else:
                    print("* MERGIFY INSTALLED BUT DISABLED "
                          "ON THIS REPOSITORY")
                break
        else:
            print("* MERGIFY DOESN'T HAVE ANY VALID OAUTH TOKENS")
    except github.UnknownObjectException:
        print("* MERGIFY SEEMS NOT INSTALLED")

    installation_token = integration.get_access_token(install_id).token

    g = github.Github(installation_token,
                      base_url="https://api.%s" % config.GITHUB_DOMAIN)
    r = g.get_repo(owner + "/" + repo)
    print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC")

    print("* CONFIGURATION:")
    try:
        mergify_config_content = rules.get_mergify_config_content(r)
    except rules.NoRules:  # pragma: no cover
        print(".mergify.yml is missing")

    print(mergify_config_content.decode())

    try:
        mergify_config = rules.UserConfigurationSchema(mergify_config_content)
    except rules.InvalidRules as e:  # pragma: no cover
        print("configuration is invalid %s" % str(e))
    else:
        pull_request_rules_raw = mergify_config["pull_request_rules"].as_dict()
        pull_request_rules_raw["rules"].extend(
            actions_runner.MERGIFY_RULE["rules"])
        pull_request_rules = rules.PullRequestRules(**pull_request_rules_raw)

    try:
        p = r.get_pull(int(pull_number))
    except github.UnknownObjectException:
        print("Wrong pull request number")
        return g, None

    mp = mergify_pull.MergifyPull(g, p, install_id)
    print("* PULL REQUEST:")
    pprint.pprint(mp.to_dict(), width=160)
    try:
        print("is_behind: %s" % mp.is_behind())
    except github.GithubException as e:
        print("Unable to know if pull request branch is behind: %s" % e)

    print("mergeable_state: %s" % mp.g_pull.mergeable_state)

    print("* MERGIFY LAST CHECKS:")
    checks = list(check_api.get_checks(p))
    for c in checks:
        if c._rawData["app"]["id"] == config.INTEGRATION_ID:
            print("[%s]: %s | %s" %
                  (c.name, c.conclusion, c.output.get("title")))
            print("> " + "\n> ".join(c.output.get("summary").split("\n")))

    print("* MERGIFY LIVE MATCHES:")
    match = pull_request_rules.get_pull_request_rule(mp)
    summary_title, summary = actions_runner.gen_summary(
        "refresh", {}, mp, match)
    print("> %s" % summary_title)
    print(summary)

    return g, p
Esempio n. 24
0
def job_filter_and_dispatch(event_type, event_id, data):
    if "installation" not in data:
        subscription = {"subscription_active": "Unknown",
                        "subscription_reason": "No"}
    else:
        subscription = sub_utils.get_subscription(
            utils.get_redis_for_cache(), data["installation"]["id"])

    if "installation" not in data:
        msg_action = "ignored (no installation id)"

    elif not subscription["tokens"]:
        msg_action = "ignored (no token)"

    elif event_type == "installation" and data["action"] == "deleted":
        # TODO(sileht): move out this engine V1 related code
        key = "queues~%s~*~*~*~*" % data["installation"]["id"]
        utils.get_redis_for_cache().delete(key)
        msg_action = "handled, cache cleaned"

    elif (event_type == "installation_repositories" and
          data["action"] == "removed"):
        for repository in data["repositories_removed"]:
            # TODO(sileht): move out this engine V1 related code
            key = "queues~%s~%s~%s~*~*" % (
                data["installation"]["id"],
                data["installation"]["account"]["login"].lower(),
                repository["name"].lower()
            )
            utils.get_redis_for_cache().delete(key)

        msg_action = "handled, cache cleaned"

    elif event_type in ["installation", "installation_repositories"]:
        msg_action = "ignored (action %s)" % data["action"]

    elif event_type in ["pull_request", "pull_request_review", "status",
                        "check_suite", "check_run"]:

        if data["repository"]["archived"]:  # pragma: no cover
            msg_action = "ignored (repository archived)"

        elif event_type == "status" and data["state"] == "pending":
            msg_action = "ignored (state pending)"

        elif (event_type in ["check_run", "check_suite"] and
              data[event_type]["app"]["id"] == config.INTEGRATION_ID and
              data["action"] != "rerequested"):
            msg_action = "ignored (mergify %s)" % event_type

        elif (event_type == "pull_request" and data["action"] not in [
                "opened", "reopened", "closed", "synchronize",
                "labeled", "unlabeled", "edited", "locked", "unlocked"]):
            msg_action = "ignored (action %s)" % data["action"]

        else:
            engine.run.s(event_type, data).apply_async()
            msg_action = "pushed to backend"

            if event_type == "pull_request":
                msg_action += ", action: %s" % data["action"]

            elif event_type == "pull_request_review":
                msg_action += ", action: %s, review-state: %s" % (
                    data["action"], data["review"]["state"])

            elif event_type == "pull_request_review_comment":
                msg_action += ", action: %s, review-state: %s" % (
                    data["action"], data["comment"]["position"])

            elif event_type == "status":
                msg_action += ", ci-status: %s, sha: %s" % (
                    data["state"], data["sha"])

            elif event_type in ["check_run", "check_suite"]:
                msg_action += (
                    ", action: %s, status: %s, conclusion: %s, sha: %s" % (
                        data["action"],
                        data[event_type]["status"],
                        data[event_type]["conclusion"],
                        data[event_type]["head_sha"]))
    else:
        msg_action = "ignored (unexpected event_type)"

    if "repository" in data:
        installation_id = data["installation"]["id"]
        repo_name = data["repository"]["full_name"]
        private = data["repository"]["private"]
    elif "installation" in data:
        installation_id = data["installation"]["id"],
        repo_name = data["installation"]["account"]["login"]
        private = "Unknown yet"
    else:
        installation_id = "Unknown"
        repo_name = "Unknown"
        private = "Unknown"

    LOG.info('GithubApp event %s', msg_action,
             event_type=event_type,
             event_id=event_id,
             install_id=installation_id,
             sender=data["sender"]["login"],
             repository=repo_name,
             private=private,
             subscription_active=subscription["subscription_active"],
             subscription_reason=subscription["subscription_reason"])
Esempio n. 25
0
def report(url):
    redis = utils.get_redis_for_cache()
    path = url.replace("https://github.com/", "")
    owner, repo, _, pull_number = path.split("/")

    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)
    install_id = utils.get_installation_id(integration, owner)

    print("* INSTALLATION ID: %s" % install_id)

    cached_sub = sub_utils.get_subscription(redis, install_id)
    db_sub = sub_utils._retrieve_subscription_from_db(install_id)
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    print("* SUB DETAIL: %s" % db_sub["subscription_reason"])

    print("* NUMBER OF CACHED TOKENS: %d" % len(cached_sub["tokens"]))

    try:
        for login, token in cached_sub["tokens"].items():
            try:
                repos = get_repositories_setuped(token, install_id)
            except github.BadCredentialsException:
                print("** token for %s invalid" % login)
            except github.GithubException as e:
                if e.status != 401:
                    raise
                print("** token for %s invalid" % login)
            else:
                if any((r["full_name"] == owner + "/" + repo) for r in repos):
                    print("* MERGIFY INSTALLED AND ENABLED ON THIS REPOSITORY")
                else:
                    print("* MERGIFY INSTALLED BUT DISABLED "
                          "ON THIS REPOSITORY")
                break
        else:
            print("* MERGIFY DOESN'T HAVE ANY VALID OAUTH TOKENS")
    except github.UnknownObjectException:
        print("* MERGIFY SEEMS NOT INSTALLED")

    installation_token = integration.get_access_token(install_id).token

    g = github.Github(installation_token,
                      base_url="https://api.%s" % config.GITHUB_DOMAIN)
    r = g.get_repo(owner + "/" + repo)
    print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC")
    p = r.get_pull(int(pull_number))

    print("* CONFIGURATION:")
    print(r.get_contents(".mergify.yml").decoded_content.decode())

    mp = mergify_pull.MergifyPull(g, p, install_id)
    print("* PULL REQUEST:")
    pprint.pprint(mp.to_dict(), width=160)
    try:
        print("is_behind: %s" % mp.is_behind())
    except github.GithubException as e:
        print("Unable to know if pull request branch is behind: %s" % e)

    print("mergeable_state: %s" % mp.g_pull.mergeable_state)

    print("* MERGIFY STATUSES:")
    commit = p.base.repo.get_commit(p.head.sha)
    for s in commit.get_combined_status().statuses:
        if s.context.startswith("mergify"):
            print("[%s]: %s" % (s.context, s.state))

    print("* MERGIFY CHECKS:")
    checks = list(check_api.get_checks(p))
    for c in checks:
        if c._rawData['app']['id'] == config.INTEGRATION_ID:
            print("[%s]: %s | %s" %
                  (c.name, c.conclusion, c.output.get("title")))
            print("> " + "\n> ".join(c.output.get("summary").split("\n")))
    return g, p
Esempio n. 26
0
def collect_metrics():
    redis = utils.get_redis_for_cache()
    integration = github.GithubIntegration(config.INTEGRATION_ID,
                                           config.PRIVATE_KEY)

    installations = collections.defaultdict(int)
    repositories_per_installation = collections.defaultdict(int)
    users_per_installation = collections.defaultdict(int)

    LOG.info("GitHub Polling started")

    redis.delete("badges.tmp")

    for installation in utils.get_installations(integration):
        try:
            _id = installation["id"]
            target_type = installation["target_type"]
            account = installation["account"]["login"]

            LOG.info("Get subscription", account=account)
            subscribed = sub_utils.get_subscription(redis, _id)["subscribed"]

            installations[(subscribed, target_type)] += 1

            token = integration.get_access_token(_id).token
            g = github.Github(token,
                              base_url="https://api.%s" % config.GITHUB_DOMAIN)

            if installation["target_type"] == "Organization":
                LOG.info("Get members",
                         install=installation["account"]["login"])
                org = g.get_organization(installation["account"]["login"])
                value = len(list(org.get_members()))

                users_per_installation[(subscribed, target_type,
                                        account)] = value
            else:
                users_per_installation[(subscribed, target_type, account)] = 1

            LOG.info("Get repos", account=account)

            repositories = sorted(g.get_installation(_id).get_repos(),
                                  key=operator.attrgetter("private"))
            for private, repos in itertools.groupby(
                    repositories, key=operator.attrgetter("private")):

                configured_repos = 0
                unconfigured_repos = 0
                for repo in repos:
                    try:
                        repo.get_contents(".mergify.yml")
                        configured_repos += 1
                        redis.sadd("badges.tmp", repo.full_name)
                    except github.GithubException as e:
                        if e.status >= 500:  # pragma: no cover
                            raise
                        unconfigured_repos += 1

                repositories_per_installation[(subscribed, target_type,
                                               account, private,
                                               True)] = configured_repos
                repositories_per_installation[(subscribed, target_type,
                                               account, private,
                                               False)] = unconfigured_repos
        except github.GithubException as e:
            # Ignore rate limit/abuse
            if e.status != 403:
                raise

    LOG.info("GitHub Polling finished")

    # NOTE(sileht): Prometheus can scrape data during our loop. So make it fast
    # to ensure we always have the good values.
    # Also we can't known which labels we should delete from the Gauge,
    # that's why we delete all of them to re-add them.
    # And prometheus_client doesn't provide API to that, so we just
    # override _metrics
    set_gauges(INSTALLATIONS, installations)
    set_gauges(USERS_PER_INSTALLATION, users_per_installation)
    set_gauges(REPOSITORIES_PER_INSTALLATION, repositories_per_installation)

    if redis.exists("badges.tmp"):
        redis.rename("badges.tmp", "badges")

    LOG.info("Gauges and badges cache updated")
Esempio n. 27
0
def run(event_type, data):
    """Everything starts here."""
    installation_id = data["installation"]["id"]
    installation_token = utils.get_installation_token(installation_id)
    if not installation_token:
        return

    g = github.Github(installation_token,
                      base_url="https://api.%s" % config.GITHUB_DOMAIN)

    if config.LOG_RATELIMIT:  # pragma: no cover
        rate = g.get_rate_limit().rate
        LOG.info("ratelimit: %s/%s, reset at %s",
                 rate.remaining, rate.limit, rate.reset,
                 repository=data["repository"]["name"])

    repo = g.get_repo(data["repository"]["owner"]["login"] + "/" +
                      data["repository"]["name"])

    event_pull = get_github_pull_from_event(repo, event_type, data)

    if not event_pull:  # pragma: no cover
        LOG.info("No pull request found in the event %s, "
                 "ignoring", event_type)
        return

    LOG.info("Pull request found in the event %s", event_type,
             repo=repo.full_name,
             pull_request=event_pull)

    subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                              installation_id)

    if repo.private and not subscription["subscription_active"]:
        check_api.set_check_run(
            event_pull, "Summary",
            "completed", "failure", output={
                "title": "Mergify is disabled",
                "summary": subscription["subscription_reason"],
            })
        return

    if ("base" not in event_pull.raw_data or
            "repo" not in event_pull.raw_data["base"] or
            len(list(event_pull.raw_data["base"]["repo"].keys())) < 70):
        LOG.warning("the pull request payload looks suspicious",
                    event_type=event_type,
                    data=data,
                    pull_request=event_pull.raw_data,
                    repo=repo.fullname)

    if (event_type == "status" and
            event_pull.head.sha != data["sha"]):  # pragma: no cover
        LOG.info("No need to proceed queue (got status of an old commit)",
                 repo=repo.full_name,
                 pull_request=event_pull)
        return

    elif (event_type in ["status", "check_suite", "check_run"] and
          event_pull.merged):  # pragma: no cover
        LOG.info("No need to proceed queue (got status of a merged "
                 "pull request)",
                 repo=repo.full_name,
                 pull_request=event_pull)
        return
    elif (event_type in ["check_suite", "check_run"] and
          event_pull.head.sha != data[event_type]["head_sha"]
          ):  # pragma: no cover
        LOG.info("No need to proceed queue (got %s of an old "
                 "commit)", event_type,
                 repo=repo.full_name,
                 pull_request=event_pull)
        return

    if check_configuration_changes(event_pull):
        LOG.info("Configuration changed, ignoring",
                 repo=repo.full_name,
                 pull_request=event_pull)
        return

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = rules.get_mergify_config(repo)
    except rules.NoRules:  # pragma: no cover
        LOG.info("No need to proceed queue (.mergify.yml is missing)",
                 repo=repo.full_name,
                 pull_request=event_pull)
        return
    except rules.InvalidRules as e:  # pragma: no cover
        # Not configured, post status check with the error message
        if (event_type == "pull_request" and
                data["action"] in ["opened", "synchronize"]):
            check_api.set_check_run(
                event_pull, "Summary", "completed",
                "failure", output={
                    "title": "The Mergify configuration is invalid",
                    "summary": str(e)
                })
        return

    create_metrics(event_type, data)

    v2.handle.s(
        installation_id,
        mergify_config["pull_request_rules"].as_dict(),
        event_type, data, event_pull.raw_data
    ).apply_async()
Esempio n. 28
0
def report(url):
    path = url.replace("https://github.com/", "")
    try:
        owner, repo, _, pull_number = path.split("/")
    except ValueError:
        pull_number = None
        try:
            owner, repo = path.split("/")
        except ValueError:
            print(f"Wrong URL: {url}")
            return

    slug = owner + "/" + repo

    try:
        client = github.get_client(owner, repo)
    except exceptions.MergifyNotInstalled:
        print("* Mergify is not installed there")
        return

    print("* INSTALLATION ID: %s" % client.auth.installation["id"])

    cached_sub, db_sub = utils.async_run(
        sub_utils.get_subscription(client.auth.owner_id),
        sub_utils._retrieve_subscription_from_db(client.auth.owner_id),
    )
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    report_sub(client.auth.installation["id"], slug, cached_sub,
               "ENGINE-CACHE")
    report_sub(client.auth.installation["id"], slug, db_sub, "DASHBOARD")

    repo = client.item(f"/repos/{owner}/{repo}")
    print(f"* REPOSITORY IS {'PRIVATE' if repo['private'] else 'PUBLIC'}")

    utils.async_run(report_worker_status(client.auth.owner))

    if pull_number:
        pull_raw = client.item(f"pulls/{pull_number}")
        ctxt = context.Context(
            client,
            pull_raw,
            cached_sub,
            [{
                "event_type": "mergify-debugger",
                "data": {}
            }],
        )

        q = queue.Queue.from_context(ctxt)
        print("* QUEUES: %s" % ", ".join([f"#{p}" for p in q.get_pulls()]))

    else:
        for branch in client.items("branches"):
            q = queue.Queue(
                utils.get_redis_for_cache(),
                client.auth.installation["id"],
                client.auth.owner,
                client.auth.repo,
                branch["name"],
            )
            pulls = q.get_pulls()
            if not pulls:
                continue

            print(f"* QUEUES {branch['name']}:")

            for priority, grouped_pulls in itertools.groupby(
                    pulls, key=lambda v: q.get_config(v)["priority"]):
                try:
                    fancy_priority = helpers.PriorityAliases(priority).name
                except ValueError:
                    fancy_priority = priority
                formatted_pulls = ", ".join((f"#{p}" for p in grouped_pulls))
                print(f"** {formatted_pulls} (priority: {fancy_priority})")

    print("* CONFIGURATION:")
    try:
        filename, mergify_config_content = rules.get_mergify_config_content(
            client)
    except rules.NoRules:  # pragma: no cover
        print(".mergify.yml is missing")
        pull_request_rules = None
    else:
        print(f"Config filename: {filename}")
        print(mergify_config_content.decode())
        try:
            mergify_config = rules.UserConfigurationSchema(
                mergify_config_content)
        except rules.InvalidRules as e:  # pragma: no cover
            print("configuration is invalid %s" % str(e))
        else:
            pull_request_rules_raw = mergify_config[
                "pull_request_rules"].as_dict()
            pull_request_rules = rules.PullRequestRules.from_list(
                pull_request_rules_raw["rules"] + engine.MERGIFY_RULE["rules"])

    if pull_number:
        print("* PULL REQUEST:")
        pr_data = dict(ctxt.pull_request.items())
        pprint.pprint(pr_data, width=160)

        print("is_behind: %s" % ctxt.is_behind)

        print("mergeable_state: %s" % ctxt.pull["mergeable_state"])

        print("* MERGIFY LAST CHECKS:")
        for c in ctxt.pull_engine_check_runs:
            print("[%s]: %s | %s" %
                  (c["name"], c["conclusion"], c["output"].get("title")))
            print("> " + "\n> ".join(c["output"].get("summary").split("\n")))

        if pull_request_rules is not None:
            print("* MERGIFY LIVE MATCHES:")
            match = pull_request_rules.get_pull_request_rule(ctxt)
            summary_title, summary = actions_runner.gen_summary(ctxt, match)
            print("> %s" % summary_title)
            print(summary)

        return ctxt
    else:
        return client