Example #1
0
    async def test_checks_feature_disabled(self):
        self.subscription = subscription.Subscription(
            utils.create_aredis_for_cache(max_idle_time=0),
            config.INSTALLATION_ID,
            self.SUBSCRIPTION_ACTIVE,
            "You're not nice",
            frozenset(
                getattr(subscription.Features, f)
                for f in subscription.Features.__members__
                if f is not subscription.Features.CUSTOM_CHECKS
            )
            if self.SUBSCRIPTION_ACTIVE
            else frozenset(),
        )
        await self.subscription.save_subscription_to_cache()

        rules = {
            "pull_request_rules": [
                {
                    "name": "body need sentry ticket",
                    "conditions": [
                        f"base={self.master_branch_name}",
                        "#title>10",
                        "#title<50",
                        "#body<4096",
                        "#files<100",
                        "body~=(?m)^(Fixes|Related|Closes) (MERGIFY-ENGINE|MRGFY)-",
                        "-label=ignore-guideline",
                    ],
                    "actions": {"post_check": {}},
                }
            ]
        }

        await self.setup_repo(yaml.dump(rules))
        p, _ = await self.create_pr()
        await self.run_engine()
        p = await self.get_pull(p["number"])

        ctxt = await context.Context.create(self.repository_ctxt, p, [])
        sorted_checks = sorted(
            await ctxt.pull_engine_check_runs, key=operator.itemgetter("name")
        )
        assert len(sorted_checks) == 2
        check = sorted_checks[0]
        assert "action_required" == check["conclusion"]
        assert "Custom checks are disabled" == check["output"]["title"]
Example #2
0
    async def start(self):
        self._stopping.clear()

        self._redis_stream = utils.create_aredis_for_stream()
        self._redis_cache = utils.create_aredis_for_cache()

        if "stream" in self.enabled_services:
            worker_ids = self.get_worker_ids()
            LOG.info("workers starting", count=len(worker_ids))
            for worker_id in worker_ids:
                self._worker_tasks.append(
                    asyncio.create_task(self.stream_worker_task(worker_id)))
            LOG.info("workers started", count=len(worker_ids))

        if "stream-monitoring" in self.enabled_services:
            LOG.info("monitoring starting")
            self._stream_monitoring_task = asyncio.create_task(
                self.monitoring_task())
            LOG.info("monitoring started")
Example #3
0
    async def asyncSetUp(self):
        super(FunctionalTestBase, self).setUp()
        self.existing_labels: typing.List[str] = []
        self.protected_branches: typing.Set[str] = set()
        self.pr_counter: int = 0
        self.git_counter: int = 0
        self.cassette_library_dir = os.path.join(CASSETTE_LIBRARY_DIR_BASE,
                                                 self.__class__.__name__,
                                                 self._testMethodName)

        # Recording stuffs
        if RECORD:
            if os.path.exists(self.cassette_library_dir):
                shutil.rmtree(self.cassette_library_dir)
            os.makedirs(self.cassette_library_dir)

        self.recorder = vcr.VCR(
            cassette_library_dir=self.cassette_library_dir,
            record_mode="all" if RECORD else "none",
            match_on=["method", "uri"],
            ignore_localhost=True,
            filter_headers=[
                ("Authorization", "<TOKEN>"),
                ("X-Hub-Signature", "<SIGNATURE>"),
                ("User-Agent", None),
                ("Accept-Encoding", None),
                ("Connection", None),
            ],
            before_record_response=self.response_filter,
        )

        if RECORD:
            github.CachedToken.STORAGE = {}
        else:
            # Never expire token during replay
            mock.patch.object(github_app,
                              "get_or_create_jwt",
                              return_value="<TOKEN>").start()
            mock.patch.object(
                github.GithubAppInstallationAuth,
                "get_access_token",
                return_value="<TOKEN>",
            ).start()

            # NOTE(sileht): httpx pyvcr stubs does not replay auth_flow as it directly patch client.send()
            # So anything occurring during auth_flow have to be mocked during replay
            def get_auth(owner_name=None, owner_id=None, auth=None):
                if auth is None:
                    auth = github.get_auth(owner_name, owner_id)
                    auth.installation = {
                        "id": config.INSTALLATION_ID,
                    }
                    auth.permissions_need_to_be_updated = False
                    auth.owner_id = config.TESTING_ORGANIZATION_ID
                    auth.owner = config.TESTING_ORGANIZATION
                return auth

            def github_aclient(owner_name=None, owner_id=None, auth=None):
                return github.AsyncGithubInstallationClient(
                    get_auth(owner_name, owner_id, auth))

            mock.patch.object(github, "aget_client", github_aclient).start()

        mock.patch.object(branch_updater.gitter, "Gitter",
                          self.get_gitter).start()
        mock.patch.object(duplicate_pull.gitter, "Gitter",
                          self.get_gitter).start()

        if not RECORD:
            # NOTE(sileht): Don't wait exponentialy during replay
            mock.patch.object(context.Context._ensure_complete.retry, "wait",
                              None).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        branch_prefix_path = os.path.join(self.cassette_library_dir,
                                          "branch_prefix")

        if RECORD:
            self.BRANCH_PREFIX = datetime.datetime.utcnow().strftime(
                "%Y%m%d%H%M%S")
            with open(branch_prefix_path, "w") as f:
                f.write(self.BRANCH_PREFIX)
        else:
            with open(branch_prefix_path, "r") as f:
                self.BRANCH_PREFIX = f.read()

        self.master_branch_name = self.get_full_branch_name("master")

        self.git = self.get_gitter(LOG)
        await self.git.init()
        self.addAsyncCleanup(self.git.cleanup)

        await root.startup()
        self.app = httpx.AsyncClient(app=root.app, base_url="http://localhost")

        await self.clear_redis_cache()
        self.redis_cache = utils.create_aredis_for_cache(max_idle_time=0)
        self.subscription = subscription.Subscription(
            self.redis_cache,
            config.TESTING_ORGANIZATION_ID,
            self.SUBSCRIPTION_ACTIVE,
            "You're not nice",
            frozenset(
                getattr(subscription.Features, f)
                for f in subscription.Features.__members__)
            if self.SUBSCRIPTION_ACTIVE else frozenset(),
        )
        await self.subscription._save_subscription_to_cache()
        self.user_tokens = user_tokens.UserTokens(
            self.redis_cache,
            config.TESTING_ORGANIZATION_ID,
            {
                "mergify-test1": config.ORG_ADMIN_GITHUB_APP_OAUTH_TOKEN,
                "mergify-test3": config.ORG_USER_PERSONAL_TOKEN,
            },
        )
        await self.user_tokens.save_to_cache()

        # Let's start recording
        cassette = self.recorder.use_cassette("http.json")
        cassette.__enter__()
        self.addCleanup(cassette.__exit__)

        self.client_integration = github.aget_client(
            config.TESTING_ORGANIZATION, config.TESTING_ORGANIZATION_ID)
        self.client_admin = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=config.ORG_ADMIN_PERSONAL_TOKEN))
        self.client_fork = github.AsyncGithubInstallationClient(
            auth=github.GithubTokenAuth(token=self.FORK_PERSONAL_TOKEN))
        self.addAsyncCleanup(self.client_integration.aclose)
        self.addAsyncCleanup(self.client_admin.aclose)
        self.addAsyncCleanup(self.client_fork.aclose)

        await self.client_admin.item("/user")
        await self.client_fork.item("/user")
        if RECORD:
            assert self.client_admin.auth.owner == "mergify-test1"
            assert self.client_fork.auth.owner == "mergify-test2"
        else:
            self.client_admin.auth.owner = "mergify-test1"
            self.client_fork.auth.owner = "mergify-test2"

        self.url_main = f"/repos/mergifyio-testing/{self.REPO_NAME}"
        self.url_fork = f"/repos/{self.client_fork.auth.owner}/{self.REPO_NAME}"
        self.git_main = f"{config.GITHUB_URL}/mergifyio-testing/{self.REPO_NAME}"
        self.git_fork = (
            f"{config.GITHUB_URL}/{self.client_fork.auth.owner}/{self.REPO_NAME}"
        )

        self.installation_ctxt = context.Installation(
            config.TESTING_ORGANIZATION_ID,
            config.TESTING_ORGANIZATION,
            self.subscription,
            self.client_integration,
            self.redis_cache,
        )
        self.repository_ctxt = context.Repository(self.installation_ctxt,
                                                  self.REPO_NAME, self.REPO_ID)

        real_get_subscription = subscription.Subscription.get_subscription

        async def fake_retrieve_subscription_from_db(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return self.subscription
            return subscription.Subscription(
                redis_cache,
                owner_id,
                False,
                "We're just testing",
                set(),
            )

        async def fake_subscription(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return await real_get_subscription(redis_cache, owner_id)
            return subscription.Subscription(
                redis_cache,
                owner_id,
                False,
                "We're just testing",
                set(),
            )

        mock.patch(
            "mergify_engine.subscription.Subscription._retrieve_subscription_from_db",
            side_effect=fake_retrieve_subscription_from_db,
        ).start()

        mock.patch(
            "mergify_engine.subscription.Subscription.get_subscription",
            side_effect=fake_subscription,
        ).start()

        async def fake_retrieve_user_tokens_from_db(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return self.user_tokens
            return user_tokens.UserTokens(redis_cache, owner_id, {})

        real_get_user_tokens = user_tokens.UserTokens.get

        async def fake_user_tokens(redis_cache, owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return await real_get_user_tokens(redis_cache, owner_id)
            return user_tokens.UserTokens(redis_cache, owner_id, {})

        mock.patch(
            "mergify_engine.user_tokens.UserTokens._retrieve_from_db",
            side_effect=fake_retrieve_user_tokens_from_db,
        ).start()

        mock.patch(
            "mergify_engine.user_tokens.UserTokens.get",
            side_effect=fake_user_tokens,
        ).start()

        self._event_reader = EventReader(self.app)
        await self._event_reader.drain()

        # NOTE(sileht): Prepare a fresh redis
        await self.clear_redis_stream()
Example #4
0
async def startup():
    global _AREDIS_STREAM, _AREDIS_CACHE
    _AREDIS_STREAM = utils.create_aredis_for_stream(
        max_connections=config.REDIS_STREAM_WEB_MAX_CONNECTIONS)
    _AREDIS_CACHE = utils.create_aredis_for_cache(
        max_connections=config.REDIS_CACHE_WEB_MAX_CONNECTIONS)
Example #5
0
async def report(
    url: str,
) -> typing.Union[context.Context, github.AsyncGithubInstallationClient, None]:
    redis_cache = utils.create_aredis_for_cache(max_idle_time=0)

    try:
        owner, repo, pull_number = _url_parser(url)
    except ValueError:
        print(f"{url} is not valid")
        return None

    try:
        client = github.aget_client(owner)
    except exceptions.MergifyNotInstalled:
        print(f"* Mergify is not installed on account {owner}")
        return None

    # Do a dumb request just to authenticate
    await client.get("/")

    if client.auth.installation is None:
        print("No installation detected")
        return None

    print(f"* INSTALLATION ID: {client.auth.installation['id']}")

    if client.auth.owner_id is None:
        raise RuntimeError("Unable to get owner_id")

    if repo is None:
        slug = None
    else:
        slug = owner + "/" + repo

    cached_sub = await subscription.Subscription.get_subscription(
        redis_cache, client.auth.owner_id)
    db_sub = await subscription.Subscription._retrieve_subscription_from_db(
        redis_cache, client.auth.owner_id)

    cached_tokens = await user_tokens.UserTokens.get(redis_cache,
                                                     client.auth.owner_id)
    db_tokens = await user_tokens.UserTokens._retrieve_from_db(
        redis_cache, client.auth.owner_id)

    print(f"* SUBSCRIBED (cache/db): {cached_sub.active} / {db_sub.active}")
    print("* Features (cache):")
    for f in db_sub.features:
        print(f"  - {f.value}")
    print("* Features (db):")
    for f in cached_sub.features:
        print(f"  - {f.value}")

    await report_dashboard_synchro(client.auth.installation["id"], cached_sub,
                                   cached_tokens, "ENGINE-CACHE", slug)
    await report_dashboard_synchro(client.auth.installation["id"], db_sub,
                                   db_tokens, "DASHBOARD", slug)

    await report_worker_status(owner)

    installation = context.Installation(client.auth.owner_id, owner,
                                        cached_sub, client, redis_cache)

    if repo is not None:
        repo_info: github_types.GitHubRepository = await client.item(
            f"/repos/{owner}/{repo}")
        repository = context.Repository(installation, repo_info["name"],
                                        repo_info["id"])

        print(
            f"* REPOSITORY IS {'PRIVATE' if repo_info['private'] else 'PUBLIC'}"
        )

        print("* CONFIGURATION:")
        mergify_config = None
        config_file = await repository.get_mergify_config_file()
        if config_file is None:
            print(".mergify.yml is missing")
        else:
            print(f"Config filename: {config_file['path']}")
            print(config_file["decoded_content"].decode())
            try:
                mergify_config = rules.get_mergify_config(config_file)
            except rules.InvalidRules as e:  # pragma: no cover
                print(f"configuration is invalid {str(e)}")
            else:
                mergify_config["pull_request_rules"].rules.extend(
                    engine.MERGIFY_BUILTIN_CONFIG["pull_request_rules"].rules)

        if pull_number is None:
            async for branch in typing.cast(
                    typing.AsyncGenerator[github_types.GitHubBranch, None],
                    client.items(f"/repos/{owner}/{repo}/branches"),
            ):
                # TODO(sileht): Add some informations on the train
                q: queue.QueueBase = naive.Queue(repository, branch["name"])
                await report_queue("QUEUES", q)

                q = merge_train.Train(repository, branch["name"])
                await q.load()
                await report_queue("TRAIN", q)

        else:
            repository = context.Repository(
                installation, github_types.GitHubRepositoryName(repo))
            ctxt = await repository.get_pull_request_context(
                github_types.GitHubPullRequestNumber(int(pull_number)))

            # FIXME queues could also be printed if no pull number given
            # TODO(sileht): display train if any
            q = await naive.Queue.from_context(ctxt)
            print(
                f"* QUEUES: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            q = await merge_train.Train.from_context(ctxt)
            print(
                f"* TRAIN: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            print("* PULL REQUEST:")
            pr_data = await ctxt.pull_request.items()
            pprint.pprint(pr_data, width=160)

            is_behind = await ctxt.is_behind
            print(f"is_behind: {is_behind}")

            print(f"mergeable_state: {ctxt.pull['mergeable_state']}")

            print("* MERGIFY LAST CHECKS:")
            for c in await ctxt.pull_engine_check_runs:
                print(
                    f"[{c['name']}]: {c['conclusion']} | {c['output'].get('title')}"
                )
                print("> " + "\n> ".join(
                    ("No Summary", ) if c["output"]["summary"] is None else
                    c["output"]["summary"].split("\n")))

            if mergify_config is not None:
                print("* MERGIFY LIVE MATCHES:")
                match = await mergify_config["pull_request_rules"
                                             ].get_pull_request_rule(ctxt)
                summary_title, summary = await actions_runner.gen_summary(
                    ctxt, match)
                print(f"[Summary]: success | {summary_title}")
                print("> " + "\n> ".join(summary.strip().split("\n")))

            return ctxt

    return client