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})
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")
def remove(account_name): """ Remove account from allowlist :param account_name: github namespace :return: """ Allowlist().remove_account(account_name)
def approve(account_name): """ Approve user who is waiting on allowlist. :param account_name: github namespace :return: """ Allowlist().approve_account(account_name)
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})
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]))
def remove(full_path: str): if full_path is None: full_path = RepoUrl().convert(construct_path()) Allowlist().remove_namespace(full_path)
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
def allowlist(): w = Allowlist() return w
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()