def _check_issue_comment_event( self, event: Union[IssueCommentEvent, IssueCommentGitlabEvent], project: GitProject, service_config: ServiceConfig, job_configs: Iterable[JobConfig], ) -> bool: actor_name = event.actor if not actor_name: raise KeyError( f"Failed to get login of the actor from {type(event)}") project_url = self._strip_protocol_and_add_git(event.project_url) namespace_approved = self.is_approved(project_url) user_approved = project.can_merge_pr(actor_name) if namespace_approved and user_approved: return True msg = (f"Project {project_url} is not on our allowlist!" if not namespace_approved else f"Account {actor_name} has no write access!") logger.debug(msg) project.get_issue(event.issue_id).comment(msg) return False
def create_issue_if_needed(project: GitProject, title: str, message: str) -> Optional[Issue]: # TODO: Improve filtering issues = project.get_issue_list() title = f"[packit] {title}" if any(title in issue.title for issue in issues): return None # TODO: store in DB return project.create_issue(title=title, body=message)
def _check_pr_event( self, event: Union[PullRequestGithubEvent, PullRequestCommentGithubEvent, MergeRequestGitlabEvent, MergeRequestCommentGitlabEvent, ], project: GitProject, service_config: ServiceConfig, job_configs: Iterable[JobConfig], ) -> bool: actor_name = event.actor if not actor_name: raise KeyError( f"Failed to get login of the actor from {type(event)}") project_url = self._strip_protocol_and_add_git(event.project_url) namespace_approved = self.is_approved(project_url) user_approved = (project.can_merge_pr(actor_name) or project.get_pr(event.pr_id).author == actor_name) if namespace_approved and user_approved: # TODO: clear failing check when present return True msg = ( f"Project {project_url} is not on our allowlist!" if not namespace_approved else f"Account {actor_name} has no write access nor is author of PR!") logger.debug(msg) if isinstance( event, (PullRequestCommentGithubEvent, MergeRequestCommentGitlabEvent)): project.get_pr(event.pr_id).comment(msg) else: for job_config in job_configs: job_helper = CoprBuildJobHelper( service_config=service_config, package_config=event.get_package_config(), project=project, metadata=EventData.from_event_dict(event.get_dict()), db_trigger=event.db_trigger, job_config=job_config, targets_override=event.targets_override, ) msg = ("Namespace is not allowed!" if not namespace_approved else "User cannot trigger!") job_helper.report_status_to_all(description=msg, state=BaseCommitStatus.neutral, url=FAQ_URL) return False
def get_package_config_from_repo( project: GitProject, reference: Optional[str] = None, base_project: Optional[GitProject] = None, pr_id: int = None, fail_when_missing: bool = True, spec_file_path: Optional[str] = None, ): """ Get the package config and catch the invalid config scenario and possibly no-config scenario """ if not base_project and not project: return None project_to_search_in = base_project or project try: package_config: PackageConfig = get_package_config_from_repo( project=project_to_search_in, ref=reference, spec_file_path=spec_file_path, ) if not package_config and fail_when_missing: raise PackitConfigException( f"No config file found in {project_to_search_in.full_repo_name} " "on ref '{reference}'" ) except PackitConfigException as ex: if pr_id: project.pr_comment( pr_id, f"Failed to load packit config file:\n```\n{str(ex)}\n```" ) else: # TODO: filter when https://github.com/packit/ogr/issues/308 fixed issues = project.get_issue_list() if "Invalid packit config" not in [x.title for x in issues]: # TODO: store in DB message = ( f"Failed to load packit config file:\n```\n{str(ex)}\n```\n" "For more info, please check out the documentation: " "http://packit.dev/packit-as-a-service/ or contact us - " "[Packit team]" "(https://github.com/orgs/packit/teams/the-packit-team)" ) i = project.create_issue( title="[packit] Invalid config", body=message ) logger.debug(f"Created issue for invalid packit config: {i.url}") raise ex return package_config
def get_package_config_from_repo( project: GitProject, reference: Optional[str] = None, base_project: Optional[GitProject] = None, pr_id: int = None, fail_when_missing: bool = True, spec_file_path: Optional[str] = None, ) -> Optional[PackageConfig]: """ Get the package config and catch the invalid config scenario and possibly no-config scenario """ if not base_project and not project: return None project_to_search_in = base_project or project try: package_config: PackageConfig = get_package_config_from_repo( project=project_to_search_in, ref=reference, spec_file_path=spec_file_path, ) if not package_config and fail_when_missing: raise PackitMissingConfigException( f"No config file for packit (e.g. `.packit.yaml`) found in " f"{project_to_search_in.full_repo_name} on commit {reference}" ) except PackitConfigException as ex: message = ( f"{str(ex)}\n\n" if isinstance(ex, PackitMissingConfigException) else f"Failed to load packit config file:\n```\n{str(ex)}\n```\n" ) message += ( "For more info, please check out the documentation: " "https://packit.dev/docs/packit-service or contact us - " "[Packit team]" "(https://github.com/orgs/packit/teams/the-packit-team)" ) if pr_id: project.get_pr(pr_id).comment(message) elif created_issue := PackageConfigGetter.create_issue_if_needed( project, title="Invalid config", message=message ): logger.debug( f"Created issue for invalid packit config: {created_issue.url}" ) raise ex
def get_packit_config_from_repo(sourcegit_project: GitProject, branch: str) -> Optional[PackageConfig]: for config_file_name in CONFIG_FILE_NAMES: try: config_file = sourcegit_project.get_file_content( path=config_file_name, ref=branch) logger.debug( f"Found a config file '{config_file_name}' " f"on branch '{branch}' " f"of the {sourcegit_project.full_repo_name} repository.") except FileNotFoundError: logger.debug( f"The config file '{config_file_name}' " f"not found on branch '{branch}' " f"of the {sourcegit_project.full_repo_name} repository.") continue try: loaded_config = anymarkup.parse(config_file) except Exception as ex: logger.error(f"Cannot load package config '{config_file_name}'.") raise Exception(f"Cannot load package config: {ex}.") return parse_loaded_config(loaded_config=loaded_config) return None
def test_check_and_report_pr_comment_approve(whitelist): event = PullRequestCommentEvent(PullRequestAction["opened"], 0, "", "", "", "", "", "lojzo", "") gp = GitProject("", GitService(), "") flexmock(gp).should_receive("pr_comment").with_args( 0, "Account is not whitelisted!").never() assert whitelist.check_and_report(event, gp)
def build_handler(metadata=None, trigger=None): if not metadata: metadata = { "owner": "nobody", "targets": [ "fedora-29-x86_64", "fedora-30-x86_64", "fedora-31-x86_64", "fedora-rawhide-x86_64", ], } jobs = [ JobConfig( job=JobType.copr_build, trigger=trigger or JobTriggerType.pull_request, metadata=metadata, ) ] pkg_conf = PackageConfig(jobs=jobs, downstream_package_name="dummy") event = Parser.parse_pr_event(pull_request()) handler = CoprBuildHandler( config=ServiceConfig(), package_config=pkg_conf, project=GitProject("", GitService(), ""), event=event, ) handler._api = PackitAPI(ServiceConfig, pkg_conf) return handler
def test_get_package_config_from_repo_alternative_config_name(): gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").with_args(path=".packit.yaml", ref=None).and_raise( FileNotFoundError, "not found") gp.should_receive("get_file_content").with_args( path=".packit.yml", ref=None).and_return("---\nspecfile_path: packit.spec\n" "synced_files:\n" " - packit.spec\n" " - src: .packit.yaml\n" " dest: .packit2.yaml") config = PackageConfigGetter.get_package_config_from_repo( project=GitProject(repo="", service=GitService(), namespace=""), reference=None, spec_file_path="packit.spec", ) assert isinstance(config, PackageConfig) assert config.specfile_path == "packit.spec" assert config.synced_files == SyncFilesConfig(files_to_sync=[ SyncFilesItem(src="packit.spec", dest="packit.spec"), SyncFilesItem(src=".packit.yaml", dest=".packit2.yaml"), ]) assert config.create_pr
def get_package_config_from_repo(sourcegit_project: GitProject, ref: str) -> Optional[PackageConfig]: for config_file_name in CONFIG_FILE_NAMES: try: config_file_content = sourcegit_project.get_file_content( path=config_file_name, ref=ref) logger.debug( f"Found a config file '{config_file_name}' " f"on ref '{ref}' " f"of the {sourcegit_project.full_repo_name} repository.") except FileNotFoundError as ex: logger.debug( f"The config file '{config_file_name}' " f"not found on ref '{ref}' " f"of the {sourcegit_project.full_repo_name} repository." f"{ex!r}") continue try: loaded_config = safe_load(config_file_content) except Exception as ex: logger.error(f"Cannot load package config '{config_file_name}'.") raise PackitException(f"Cannot load package config: {ex}.") return parse_loaded_config(loaded_config=loaded_config, config_file_path=config_file_name) logger.warning(f"No config file found on ref '{ref}' " f"of the {sourcegit_project.full_repo_name} repository.") return None
def test_get_packit_config_from_repo(content): flexmock(GitProject).should_receive("get_file_content").and_return(content) git_project = GitProject(repo="", service=GitService(), namespace="") config = get_packit_config_from_repo(sourcegit_project=git_project, ref="") assert isinstance(config, PackageConfig) assert config.specfile_path == "packit.spec" assert set(config.synced_files) == {"packit.spec", ".packit.yaml"}
def build_helper( event: Union[PullRequestEvent, PullRequestCommentEvent, CoprBuildEvent, PushGitHubEvent, ReleaseEvent, ], metadata=None, trigger=None, jobs=None, ): if not metadata: metadata = { "owner": "nobody", "targets": [ "fedora-29-x86_64", "fedora-30-x86_64", "fedora-31-x86_64", "fedora-rawhide-x86_64", ], } jobs = jobs or [] jobs.append( JobConfig( type=JobType.copr_build, trigger=trigger or JobConfigTriggerType.pull_request, metadata=metadata, )) pkg_conf = PackageConfig(jobs=jobs, downstream_package_name="dummy") handler = CoprBuildJobHelper( config=ServiceConfig(), package_config=pkg_conf, project=GitProject("", GitService(), ""), event=event, ) handler._api = PackitAPI(ServiceConfig(), pkg_conf) return handler
def test_check_and_report_calls_method(whitelist, event, method, approved): gp = GitProject("", GitService(), "") mocked_gp = (flexmock(gp).should_receive(method).with_args( 0, "Neither account bar nor owner foo are on our whitelist!")) mocked_gp.never() if approved else mocked_gp.once() assert (whitelist.check_and_report( event, gp, config=flexmock(deployment=Deployment.stg)) is approved)
def get_package_config_from_repo(sourcegit_project: GitProject, ref: str) -> Optional[PackageConfig]: for config_file_name in CONFIG_FILE_NAMES: try: config_file_content = sourcegit_project.get_file_content( path=config_file_name, ref=ref) except FileNotFoundError: # do nothing pass else: logger.debug( f"Found a config file '{config_file_name}' " f"on ref '{ref}' " f"of the {sourcegit_project.full_repo_name} repository.") break else: logger.warning( f"No config file ({CONFIG_FILE_NAMES}) found on ref '{ref}' " f"of the {sourcegit_project.full_repo_name} repository.") return None try: loaded_config = safe_load(config_file_content) except Exception as ex: logger.error(f"Cannot load package config {config_file_name!r}. {ex}") raise PackitConfigException( f"Cannot load package config {config_file_name!r}. {ex}") return parse_loaded_config( loaded_config=loaded_config, config_file_path=config_file_name, repo_name=sourcegit_project.repo, spec_file_path=get_specfile_path_from_repo(sourcegit_project, ref), )
def test_check_and_report_pr_comment_reject(whitelist): event = PullRequestCommentEvent(PullRequestAction["opened"], 0, "brcalnik", "", "", "", "", "rakosnicek", "") gp = GitProject("", GitService(), "") flexmock(gp).should_receive("pr_comment").with_args( 0, "Neither account rakosnicek nor owner brcalnik are on our whitelist!" ).once() assert not whitelist.check_and_report(event, gp)
def get_current_branch_pr(self, git_project: GitProject) -> PullRequest: """ If the current branch is assoctiated with a PR, get it, otherwise return None """ current_branch = self.get_current_branch() pr_re = re.compile(r"^pr/(\d+)$") m = pr_re.match(current_branch) if m: pr_id = int(m.group(1)) pr = git_project.get_pr(pr_id) return pr # FIXME: could this logic be in ogr? input: branch + repo, output: pr for pr in git_project.get_pr_list(): if (pr.source_branch == current_branch and pr.author == git_project.service.user.get_username()): return pr
def test_get_packit_config_from_repo(content): flexmock(GitProject).should_receive("get_file_content").and_return(content) git_project = GitProject(repo="", service=GitService(), namespace="") config = get_packit_config_from_repo(sourcegit_project=git_project, ref="") assert isinstance(config, PackageConfig) assert Path(config.specfile_path).name == "packit.spec" assert config.synced_files == SyncFilesConfig(files_to_sync=[ SyncFilesItem(src="packit.spec", dest="packit.spec"), SyncFilesItem(src=".packit.yaml", dest=".packit2.yaml"), ])
def test_get_package_config_from_repo_spec_file_not_defined(content): gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").and_return(content) gp.should_receive("get_files").and_return(["packit.spec"]) git_project = GitProject(repo="", service=GitService(), namespace="") config = get_package_config_from_repo(project=git_project, ref="") assert isinstance(config, PackageConfig) assert Path(config.specfile_path).name == "packit.spec" assert config.create_pr
def get_package_config_from_repo( self, project: GitProject, reference: str, pr_id: int = None, fail_when_missing: bool = True, ): """ Get the package config and catch the invalid config scenario and possibly no-config scenario Static because of the easier mocking. """ try: package_config: PackageConfig = get_package_config_from_repo( project, reference ) if not package_config and fail_when_missing: raise PackitConfigException( f"No config file found in {project.full_repo_name}" ) except PackitConfigException as ex: if pr_id: project.pr_comment( pr_id, f"Failed to load packit config file:\n```\n{str(ex)}\n```" ) else: # TODO: filter when https://github.com/packit-service/ogr/issues/308 fixed issues = project.get_issue_list() if "Invalid packit config" not in [x.title for x in issues]: # TODO: store in DB message = ( f"Failed to load packit config file:\n```\n{str(ex)}\n```\n" "For more info, please check out the documentation: " "http://packit.dev/packit-as-a-service/ or contact us - " "[Packit team]" "(https://github.com/orgs/packit-service/teams/the-packit-team)" ) i = project.create_issue( title="[packit] Invalid config", body=message ) logger.debug(f"Created issue for invalid packit config: {i.url}") raise ex return package_config
def get_specfile_path_from_repo(project: GitProject) -> Optional[str]: """ Get the path of the spec file in the given repo if present. :param project: GitProject :return: str path of the spec file or None """ spec_files = project.get_files(filter_regex=r".+\.spec$") if not spec_files: logger.debug(f"No spec file found in {project.full_repo_name}") return None return spec_files[0]
def get_specfile_path_from_repo(project: GitProject): """ Get the path of the spec file in the given repo if present. :param project: GitProject :return: str path of the spec file """ try: return project.get_files(filter_regex=".*.spec")[0] except Exception as ex: logger.debug(f"Cannot find the spec file: {ex}") return None
def test_get_package_config_from_repo_not_found_exception_pr(): gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").and_raise(FileNotFoundError, "not found") gp.should_receive("pr_comment").and_return(None).once() with pytest.raises(PackitConfigException): PackageConfigGetter.get_package_config_from_repo( project=GitProject(repo="", service=GitService(), namespace=""), reference=None, pr_id=2, )
def test_get_package_config_from_repo_not_found(): gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").and_raise(FileNotFoundError, "not found") assert (PackageConfigGetter.get_package_config_from_repo( project=GitProject(repo="", service=GitService(), namespace=""), reference=None, pr_id=2, fail_when_missing=False, ) is None)
def test_check_and_report_calls_method(whitelist, event, method, approved): gp = GitProject("", GitService(), "") mocked_gp = (flexmock(gp).should_receive(method).with_args( 0, "Neither account bar nor owner foo are on our whitelist!")) mocked_gp.never() if approved else mocked_gp.once() whitelist_mock = flexmock(DBWhitelist).should_receive("get_account") if approved: whitelist_mock.and_return(DBWhitelist(status="approved_manually")) else: whitelist_mock.and_return(None) assert (whitelist.check_and_report( event, gp, config=flexmock(deployment=Deployment.stg)) is approved)
def test_get_package_config_from_repo_not_found_exception_existing_issue(): gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").and_raise(FileNotFoundError, "not found") gp.should_receive("get_issue_list").and_return( [flexmock(title="Invalid packit config")]).once() with pytest.raises(PackitConfigException): PackageConfigGetter.get_package_config_from_repo( project=GitProject(repo="", service=GitService(), namespace=""), reference=None, )
def get_specfile_path_from_repo(project: GitProject, ref: str = None) -> Optional[str]: """ Get the path of the spec file in the given repo if present. :param project: GitProject :param ref: git ref (defaults to repo's default branch) :return: str path of the spec file or None """ spec_files = project.get_files(ref=ref, filter_regex=r".+\.spec$") if not spec_files: logger.debug(f"No spec file found in {project.full_repo_name!r}") return None return spec_files[0]
def test_get_package_config_from_repo_spec_file_not_defined(content): specfile_path = "packit.spec" gp = flexmock(GitProject) gp.should_receive("full_repo_name").and_return("a/b") gp.should_receive("get_file_content").and_return(content) gp.should_receive("get_files").and_return([specfile_path]) git_project = GitProject(repo="", service=GitService(), namespace="") config = get_package_config_from_repo(project=git_project) assert isinstance(config, PackageConfig) assert Path(config.specfile_path).name == specfile_path assert config.create_pr for j in config.jobs: assert j.specfile_path == specfile_path assert j.downstream_package_name == config.downstream_package_name assert j.upstream_package_name == config.upstream_package_name
def _check_issue_comment_event( self, event: Union[IssueCommentEvent, IssueCommentGitlabEvent], project: GitProject, service_config: ServiceConfig, job_configs: Iterable[JobConfig], ) -> bool: account_name = event.user_login if not account_name: raise KeyError(f"Failed to get account_name from {type(event)}") namespace = event.repo_namespace namespace_approved = self.is_approved(namespace) user_approved = project.can_merge_pr(account_name) if namespace_approved and user_approved: return True msg = (f"Namespace {namespace} is not on our allowlist!" if not namespace_approved else f"Account {account_name} has no write access!") logger.error(msg) project.issue_comment(event.issue_id, msg) return False
def build_helper( event: Union[ PullRequestGithubEvent, PullRequestCommentGithubEvent, PushGitHubEvent, ReleaseEvent, ], metadata=None, trigger=None, jobs=None, db_trigger=None, ): if not metadata: metadata = JobMetadataConfig( targets=[ "fedora-29-x86_64", "fedora-30-x86_64", "fedora-31-x86_64", "fedora-rawhide-x86_64", ], owner="nobody", ) jobs = jobs or [] jobs.append( JobConfig( type=JobType.production_build, trigger=trigger or JobConfigTriggerType.pull_request, metadata=metadata, ) ) pkg_conf = PackageConfig(jobs=jobs, downstream_package_name="dummy") handler = KojiBuildJobHelper( service_config=ServiceConfig(), package_config=pkg_conf, job_config=pkg_conf.jobs[0], project=GitProject(repo=flexmock(), service=flexmock(), namespace=flexmock()), metadata=flexmock( trigger=event.trigger, pr_id=event.pr_id, git_ref=event.git_ref, commit_sha=event.commit_sha, identifier=event.identifier, ), db_trigger=db_trigger, ) handler._api = PackitAPI(config=ServiceConfig(), package_config=pkg_conf) return handler
def test_copr_build_success_gitlab_comment(gitlab_mr_event): helper = build_helper( event=gitlab_mr_event, db_trigger=flexmock( job_config_trigger_type=JobConfigTriggerType.pull_request, id=123), ) flexmock(BaseBuildJobHelper).should_receive( "is_gitlab_instance").and_return(True) flexmock(BaseBuildJobHelper).should_receive("base_project").and_return( GitProject( repo="the-example-repo", service=flexmock(), namespace="the-example-namespace", )) flexmock(GitProject).should_receive("request_access").and_return() flexmock(GitProject).should_receive("pr_comment").and_return() flexmock(BaseBuildJobHelper).should_receive( "is_reporting_allowed").and_return(False) flexmock(GitProject).should_receive("get_pr").and_return(flexmock()) flexmock(SRPMBuildModel).should_receive("create").and_return( SRPMBuildModel(success=True)) flexmock(CoprBuildModel).should_receive("get_or_create").and_return( CoprBuildModel(id=1)) flexmock(MergeRequestGitlabEvent).should_receive("db_trigger").and_return( flexmock()) flexmock(PackitAPI).should_receive("create_srpm").and_return("my.srpm") # copr build flexmock(CoprHelper).should_receive( "create_copr_project_if_not_exists").and_return(None) flexmock(CoprHelper).should_receive("get_copr_client").and_return( flexmock( config={"copr_url": "https://copr.fedorainfracloud.org/"}, build_proxy=flexmock().should_receive( "create_from_file").and_return( flexmock(id=2, projectname="the-project-name", ownername="the-owner")).mock(), mock_chroot_proxy=flexmock().should_receive("get_list").and_return( {target: "" for target in DEFAULT_TARGETS}).mock(), )) flexmock(Pushgateway).should_receive("push_copr_build_created") flexmock(Celery).should_receive("send_task").once() assert helper.run_copr_build()["success"]