Exemple #1
0
    def run(self) -> TaskResults:
        """
        Discover information about organization/user which wants to install packit on his repository
        Try to allowlist automatically if mapping from github username to FAS account can prove that
        user is a packager.
        :return: TaskResults
        """
        InstallationModel.create(event=self.installation_event)
        # try to add user to allowlist
        allowlist = Allowlist(
            fas_user=self.service_config.fas_user,
            fas_password=self.service_config.fas_password,
        )
        if not allowlist.add_account(self.account_login, self.sender_login):
            # Create an issue in our repository, so we are notified when someone install the app
            self.project.create_issue(
                title=
                f"{self.account_type} {self.account_login} needs to be approved.",
                body=
                (f"Hi @{self.sender_login}, we need to approve you in "
                 "order to start using Packit-as-a-Service. Someone from our team will "
                 "get back to you shortly.\n\n"
                 "For more info, please check out the documentation: "
                 "http://packit.dev/packit-as-a-service/"),
            )
            msg = f"{self.account_type} {self.account_login} needs to be approved manually!"
        else:
            msg = f"{self.account_type} {self.account_login} allowlisted!"

        logger.info(msg)
        return TaskResults(success=True, details={"msg": msg})
Exemple #2
0
def approve(full_path: Optional[str]):
    if full_path is None:
        full_path = RepoUrl().convert(construct_path())

    is_approved_before = Allowlist.is_approved(full_path)

    Allowlist().approve_namespace(full_path)
    if Allowlist.is_approved(full_path) != is_approved_before:
        click.secho(f"Namespace ‹{full_path}› has been approved.", fg="green")
    else:
        click.secho(f"Status of namespace ‹{full_path}› has not changed.",
                    fg="yellow")
Exemple #3
0
def remove(account_name):
    """
    Remove account from allowlist

    :param account_name: github namespace
    :return:
    """
    Allowlist().remove_account(account_name)
Exemple #4
0
def approve(account_name):
    """
    Approve user who is waiting on allowlist.

    :param account_name: github namespace
    :return:
    """
    Allowlist().approve_account(account_name)
Exemple #5
0
    def run(self) -> TaskResults:
        """
        Discover information about organization/user which wants to install packit on his repository
        Try to allowlist automatically if mapping from github username to FAS account can prove that
        user is a packager.
        :return: TaskResults
        """
        InstallationModel.create(event=self.installation_event)
        # try to add user to allowlist
        allowlist = Allowlist(
            fas_user=self.service_config.fas_user,
            fas_password=self.service_config.fas_password,
        )
        if not allowlist.add_namespace(f"github.com/{self.account_login}",
                                       self.sender_login):
            # Create an issue in our repository, so we are notified when someone install the app
            self.project.create_issue(
                title=
                f"{self.account_type} {self.account_login} needs to be approved.",
                body=
                (f"Hi @{self.sender_login}, we need to approve you in "
                 "order to start using Packit-as-a-Service. "
                 "We are now onboarding Fedora contributors who have a valid "
                 "[Fedora Account System](https://fedoraproject.org/wiki/Account_System) "
                 "account and have signed "
                 "[FPCA](https://fedoraproject.org/wiki/Legal"
                 ":Fedora_Project_Contributor_Agreement). "
                 "If you have such an account, please, provide it in a comment and "
                 "we'd be glad to approve you for using the service.\n\n"
                 "For more info, please check out the documentation: "
                 "https://packit.dev/docs/packit-service"),
            )
            msg = f"{self.account_type} {self.account_login} needs to be approved manually!"
        else:
            msg = f"{self.account_type} {self.account_login} allowlisted!"

        logger.info(msg)
        return TaskResults(success=True, details={"msg": msg})
Exemple #6
0
def waiting(ctx):
    click.echo("Accounts waiting for approval:")

    waiting_list = Allowlist().waiting_namespaces()
    for i, namespace in enumerate(waiting_list, 1):
        click.echo(f"{i}. {namespace}")

    if choice := click.prompt(
            "Do you wish to allowlist namespace or repository?",
            type=click.types.IntRange(min=min(1, len(waiting_list)),
                                      max=len(waiting_list)),
    ):
        click.echo()
        ctx.invoke(approve, full_path=prompt_variant(waiting_list[choice - 1]))
Exemple #7
0
def remove(full_path: str):
    if full_path is None:
        full_path = RepoUrl().convert(construct_path())

    Allowlist().remove_namespace(full_path)
Exemple #8
0
    def process_jobs(self, event: Event) -> List[TaskResults]:
        """
        Create a Celery task for a job handler (if trigger matches) for every job defined in config.
        """
        if isinstance(
                event,
                AbstractCommentEvent) and get_packit_commands_from_comment(
                    event.comment):
            # we require packit config file when event is triggered by /packit command
            event.fail_when_config_file_missing = True

        if not event.package_config:
            # this happens when service receives events for repos which don't have packit config
            # success=True - it's not an error that people don't have packit.yaml in their repo
            return [
                TaskResults.create_from(
                    success=True,
                    msg="No packit config found in the repository.",
                    job_config=None,
                    event=event,
                )
            ]

        handler_classes = get_handlers_for_event(event, event.package_config)

        if not handler_classes:
            logger.debug(
                f"There is no handler for {event} event suitable for the configuration."
            )
            return []

        allowlist = Allowlist()
        processing_results: List[TaskResults] = []

        for handler_kls in handler_classes:
            # TODO: merge to to get_handlers_for_event so
            # so we don't need to go through the similar process twice.
            job_configs = get_config_for_handler_kls(
                handler_kls=handler_kls,
                event=event,
                package_config=event.package_config,
            )

            # check allowlist approval for every job to be able to track down which jobs
            # failed because of missing allowlist approval
            if not allowlist.check_and_report(
                    event,
                    event.project,
                    service_config=self.service_config,
                    job_configs=job_configs,
            ):
                processing_results = []
                for job_config in job_configs:
                    processing_results.append(
                        TaskResults.create_from(
                            success=False,
                            msg="Account is not allowlisted!",
                            job_config=job_config,
                            event=event,
                        ))
                return processing_results

            signatures = []

            # we want to run handlers for all possible jobs, not just the first one
            for job_config in job_configs:
                handler = handler_kls(
                    package_config=event.package_config,
                    job_config=job_config,
                    event=event.get_dict(),
                )
                if not handler.pre_check():
                    continue

                if event.actor and not handler.check_if_actor_can_run_job_and_report(
                        actor=event.actor):
                    # For external contributors, we need to be more careful when running jobs.
                    # This is a handler-specific permission check
                    # for a user who trigger the action on a PR.
                    # e.g. We don't allow using internal TF for external contributors.
                    continue

                if isinstance(
                        handler,
                    (CoprBuildHandler, KojiBuildHandler, TestingFarmHandler)):
                    helper = (CoprBuildJobHelper if isinstance(
                        handler, (CoprBuildHandler,
                                  TestingFarmHandler)) else KojiBuildJobHelper)

                    job_helper = helper(
                        service_config=self.service_config,
                        package_config=event.package_config,
                        project=event.project,
                        metadata=EventData.from_event_dict(event.get_dict()),
                        db_trigger=event.db_trigger,
                        job_config=job_config,
                        targets_override=event.targets_override,
                    )

                    reporting_method = (job_helper.report_status_to_tests if
                                        isinstance(handler, TestingFarmHandler)
                                        else job_helper.report_status_to_build)

                    reporting_method(
                        description=TASK_ACCEPTED,
                        state=BaseCommitStatus.pending,
                        url="",
                    )
                    push_initial_metrics(event, handler,
                                         len(job_helper.build_targets))

                signatures.append(
                    handler_kls.get_signature(event=event, job=job_config))
                processing_results.append(
                    TaskResults.create_from(
                        success=True,
                        msg="Job created.",
                        job_config=job_config,
                        event=event,
                    ))
            # https://docs.celeryproject.org/en/stable/userguide/canvas.html#groups
            group(signatures).apply_async()

        return processing_results
Exemple #9
0
def allowlist():
    w = Allowlist()
    return w
Exemple #10
0
def test_check_and_report(allowlist: Allowlist,
                          events: List[Tuple[AbstractGithubEvent, bool]]):
    """
    :param allowlist: fixture
    :param events: fixture: [(Event, should-be-approved)]
    """
    flexmock(
        GithubProject,
        pr_comment=lambda *args, **kwargs: None,
        set_commit_status=lambda *args, **kwargs: None,
        issue_comment=lambda *args, **kwargs: None,
        get_pr=lambda *args, **kwargs: flexmock(source_project=flexmock(),
                                                author=None),
    )
    job_configs = [
        JobConfig(
            type=JobType.tests,
            trigger=JobConfigTriggerType.pull_request,
            metadata=JobMetadataConfig(targets=["fedora-rawhide"]),
        )
    ]
    flexmock(PullRequestGithubEvent).should_receive(
        "get_package_config").and_return(flexmock(jobs=job_configs, ))
    flexmock(PullRequestModel).should_receive("get_or_create").and_return(
        flexmock(job_config_trigger_type=JobConfigTriggerType.pull_request))

    git_project = GithubProject("", GithubService(), "")
    for event, is_valid in events:
        flexmock(GithubProject, can_merge_pr=lambda username: is_valid)
        flexmock(
            event,
            project=git_project).should_receive("get_dict").and_return(None)
        # needs to be included when running only `test_allowlist`
        # flexmock(event).should_receive("db_trigger").and_return(
        #     flexmock(job_config_trigger_type=job_configs[0].trigger).mock()
        # )
        flexmock(EventData).should_receive("from_event_dict").and_return(
            flexmock(commit_sha="0000000", pr_id="0"))

        if isinstance(event, PullRequestGithubEvent) and not is_valid:
            # Report the status
            flexmock(CoprHelper).should_receive("get_copr_client").and_return(
                Client(
                    config={
                        "copr_url": "https://copr.fedorainfracloud.org",
                        "username": "******",
                    }))
            flexmock(LocalProject).should_receive(
                "refresh_the_arguments").and_return(None)
            flexmock(LocalProject).should_receive("checkout_pr").and_return(
                None)
            flexmock(StatusReporter).should_receive("report").with_args(
                description="Namespace is not allowed!",
                state=CommitStatus.error,
                url=FAQ_URL,
                check_names=[EXPECTED_TESTING_FARM_CHECK_NAME],
            ).once()
        flexmock(packit_service.worker.build.copr_build).should_receive(
            "get_valid_build_targets").and_return({
                "fedora-rawhide-x86_64",
            })

        # get_account returns the allowlist object if it exists
        # returns nothing if it isn't allowlisted
        # then inside the allowlist.py file, a function checks if the status is
        # one of the approved statuses

        # this exact code is used twice above but mypy has an issue with this one only
        allowlist_mock = flexmock(DBAllowlist).should_receive("get_account")
        if not TYPE_CHECKING:
            if is_valid:
                allowlist_mock.and_return(
                    DBAllowlist(status="approved_manually"))
            else:
                allowlist_mock.and_return(None)

        assert (allowlist.check_and_report(
            event,
            git_project,
            service_config=flexmock(
                deployment=Deployment.stg,
                command_handler_work_dir="",
                admins=["admin"],
            ),
            job_configs=job_configs,
        ) is is_valid)
def test_strip_protocol_and_add_git(url: str, expected_url: str) -> None:
    assert Allowlist._strip_protocol_and_add_git(url) == expected_url
def test_check_and_report(
    allowlist: Allowlist,
    allowlist_entries,
    events: Iterable[Tuple[AbstractGithubEvent, bool, Iterable[str]]],
):
    """
    :param allowlist: fixture
    :param events: fixture: [(Event, should-be-approved)]
    """
    flexmock(
        GithubProject,
        create_check_run=lambda *args, **kwargs: None,
        get_issue=lambda *args, **kwargs: flexmock(comment=lambda *args, **
                                                   kwargs: None),
        get_pr=lambda *args, **kwargs: flexmock(source_project=flexmock(),
                                                author=None,
                                                comment=lambda *args, **kwargs:
                                                None),
    )
    job_configs = [
        JobConfig(
            type=JobType.tests,
            trigger=JobConfigTriggerType.pull_request,
            metadata=JobMetadataConfig(_targets=["fedora-rawhide"]),
        )
    ]
    flexmock(PullRequestGithubEvent).should_receive(
        "get_package_config").and_return(flexmock(jobs=job_configs, ))
    flexmock(PullRequestModel).should_receive("get_or_create").and_return(
        flexmock(
            job_config_trigger_type=JobConfigTriggerType.pull_request,
            id=123,
            job_trigger_model_type=JobTriggerModelType.pull_request,
        ))

    flexmock(JobTriggerModel).should_receive("get_or_create").with_args(
        type=JobTriggerModelType.pull_request, trigger_id=123).and_return(
            flexmock(id=2, type=JobTriggerModelType.pull_request))

    git_project = GithubProject("", GithubService(), "")
    for event, is_valid, resolved_through in events:
        flexmock(GithubProject, can_merge_pr=lambda username: is_valid)
        flexmock(
            event,
            project=git_project).should_receive("get_dict").and_return(None)
        # needs to be included when running only `test_allowlist`
        # flexmock(event).should_receive("db_trigger").and_return(
        #     flexmock(job_config_trigger_type=job_configs[0].trigger).mock()
        # )
        flexmock(EventData).should_receive("from_event_dict").and_return(
            flexmock(commit_sha="0000000", pr_id="0"))

        if isinstance(event, PullRequestGithubEvent) and not is_valid:
            # Report the status
            flexmock(CoprHelper).should_receive("get_copr_client").and_return(
                Client(
                    config={
                        "copr_url": "https://copr.fedorainfracloud.org",
                        "username": "******",
                    }))
            flexmock(LocalProject).should_receive(
                "refresh_the_arguments").and_return(None)
            flexmock(LocalProject).should_receive("checkout_pr").and_return(
                None)
            flexmock(StatusReporter).should_receive("report").with_args(
                description="Namespace is not allowed!",
                state=BaseCommitStatus.neutral,
                url=FAQ_URL,
                check_names=[EXPECTED_TESTING_FARM_CHECK_NAME],
                markdown_content=None,
            ).once()
        flexmock(packit_service.worker.build.copr_build).should_receive(
            "get_valid_build_targets").and_return({
                "fedora-rawhide-x86_64",
            })
        mock_model(allowlist_entries, resolved_through)

        assert (allowlist.check_and_report(
            event,
            git_project,
            service_config=flexmock(
                deployment=Deployment.stg,
                command_handler_work_dir="",
                admins=["admin"],
            ),
            job_configs=job_configs,
        ) is is_valid)
def allowlist():
    return Allowlist()