class ProposeUpdate(PackitUnittestOgr): def setUp(self): super().setUp() self.api = PackitAPI( config=self.conf, package_config=self.pc, upstream_local_project=self.lp ) self.api._up = self.upstream self.api._dg = self.dg # Do not upload package, because no credentials given in CI flexmock(self.api).should_receive("_handle_sources").and_return(None) self.set_git_user() @unittest.skip( "Issue in ogr causing that User is not stored in persistent yaml files for pagure" ) def test_propose_update(self): # change specfile little bit to have there some change specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") with open(specfile_location, "a") as myfile: myfile.write("# test text") check_output( f"cd {self.lp.working_dir}; git commit -m 'test change' python-ogr.spec", shell=True, ) self.api.sync_release("master")
def test_basic_local_update_direct_push(upstream_distgit_remote, mock_remote_functionality_upstream): """ basic propose-update test: mock remote API, use local upstream and dist-git """ upstream, distgit, remote_dir = upstream_distgit_remote with cwd(upstream): c = get_test_config() pc = get_local_package_config(str(upstream)) pc.upstream_project_url = str(upstream) pc.dist_git_clone_path = str(distgit) up_lp = LocalProject(working_dir=str(upstream)) api = PackitAPI(c, pc, up_lp) api.sync_release("master", "0.1.0", create_pr=False) remote_dir_clone = Path(f"{remote_dir}-clone") subprocess.check_call( ["git", "clone", remote_dir, str(remote_dir_clone)], cwd=str(remote_dir_clone.parent), ) spec = get_specfile(str(remote_dir_clone / "beer.spec")) assert spec.get_version() == "0.1.0" assert (remote_dir_clone / "README.packit").is_file()
def test_update_on_cockpit_ostree(cockpit_ostree): def mocked_new_sources(sources=None): if not Path(sources).is_file(): raise RuntimeError("archive does not exist") flexmock(FedPKG, init_ticket=lambda x=None: None, new_sources=mocked_new_sources) flexmock( DistGit, push_to_fork=lambda *args, **kwargs: None, is_archive_in_lookaside_cache=lambda archive_path: False, ) flexmock( PackitAPI, push_and_create_pr=lambda pr_title, pr_description, dist_git_branch: None, ) pc = get_local_package_config(str(cockpit_ostree)) up_lp = LocalProject(working_dir=str(cockpit_ostree)) c = get_test_config() api = PackitAPI(c, pc, up_lp) with cwd(cockpit_ostree): api.sync_release( "master", use_local_content=False, version="179", force_new_sources=False, create_pr=True, ) assert api.dg.download_upstream_archive().is_file()
class ProposeDownstreamHandler(JobHandler): type = JobType.propose_downstream triggers = [TheJobTriggerType.release] task_name = TaskName.propose_downstream def run(self) -> TaskResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.service_config.command_handler_work_dir, ) self.api = PackitAPI(self.service_config, self.job_config, self.local_project) errors = {} for branch in get_branches(*self.job_config.metadata.dist_git_branches, default="master"): try: self.api.sync_release(dist_git_branch=branch, version=self.data.tag_name) except Exception as ex: sentry_integration.send_to_sentry(ex) errors[branch] = str(ex) if errors: branch_errors = "" for branch, err in sorted( errors.items(), key=lambda branch_error: branch_error[0]): err_without_new_lines = err.replace("\n", " ") branch_errors += f"| `{branch}` | `{err_without_new_lines}` |\n" body_msg = ( f"Packit failed on creating pull-requests in dist-git:\n\n" f"| dist-git branch | error |\n" f"| --------------- | ----- |\n" f"{branch_errors}\n\n" "You can re-trigger the update by adding `/packit propose-update`" " to the issue comment.\n") self.project.create_issue( title= f"[packit] Propose update failed for release {self.data.tag_name}", body=body_msg, ) return TaskResults( success=False, details={ "msg": "Propose update failed.", "errors": errors }, ) return TaskResults(success=True, details={})
def update(config, dist_git_path, upstream_git_path, dist_git_branch, repo): """ Release current upstream release into Fedora """ package_config = get_local_package_config(directory=repo.working_dir) package_config.downstream_project_url = dist_git_path package_config.upstream_project_url = upstream_git_path api = PackitAPI(config=config, package_config=package_config) api.sync_release(dist_git_branch)
def run(self): """ Sync the upstream release to dist-git as a pull request. """ version = self.event["release"]["tag_name"] local_project = LocalProject(git_project=self.project) api = PackitAPI(self.config, self.package_config, local_project) api.sync_release( dist_git_branch=self.job.metadata.get("dist-git-branch", "master"), version=version, )
class GithubReleaseHandler(AbstractGithubJobHandler): name = JobType.propose_downstream triggers = [JobTriggerType.release] event: ReleaseEvent def __init__(self, config: ServiceConfig, job: JobConfig, release_event: ReleaseEvent): super().__init__(config=config, job=job, event=release_event) self.project: GitProject = release_event.get_project() self.package_config: PackageConfig = get_package_config_from_repo( self.project, release_event.tag_name) if not self.package_config: raise ValueError( f"No config file found in {self.project.full_repo_name}") self.package_config.upstream_project_url = release_event.project_url def run(self) -> HandlerResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir) self.api = PackitAPI(self.config, self.package_config, self.local_project) errors = [] for branch in get_branches( self.job.metadata.get("dist-git-branch", "master")): try: self.api.sync_release(dist_git_branch=branch, version=self.event.tag_name) except Exception as ex: sentry_integration.send_to_sentry(ex) errors.append( f"Propose update for branch {branch} failed: {ex}") if errors: return HandlerResults( success=False, details={ "msg": "Propose update failed.", "errors": errors }, ) return HandlerResults(success=True, details={})
def test_basic_local_update(upstream_n_distgit, mock_upstream_remote_functionality): """ basic propose-update test: mock remote API, use local upstream and dist-git """ u, d = upstream_n_distgit chdir(u) c = get_test_config() pc = get_local_package_config(str(u)) pc.upstream_project_url = str(u) pc.downstream_project_url = str(d) api = PackitAPI(c, pc) api.sync_release("master", "0.1.0") assert (d / TARBALL_NAME).is_file() spec = SpecFile(str(d / "beer.spec"), None) assert spec.get_version() == "0.1.0"
def sync_upstream_release( self, package_config: PackageConfig, version: Optional[str], dist_git_branch: str, ): """ Sync the upstream release to the distgit pull-request. :param package_config: PackageConfig :param version: not used now, str :param dist_git_branch: str """ logger.info("syncing the upstream code to downstream") packit_api = PackitAPI(config=self.config, package_config=package_config) packit_api.sync_release(dist_git_branch=dist_git_branch, version=version)
def test_update_on_cockpit_ostree_pr_exists(cockpit_ostree): upstream_path, dist_git_path = cockpit_ostree def mocked_new_sources(sources=None): if not Path(sources).is_file(): raise RuntimeError("archive does not exist") flexmock(PkgTool, new_sources=mocked_new_sources) flexmock(PackitAPI, init_kerberos_ticket=lambda: None) flexmock( DistGit, push_to_fork=lambda *args, **kwargs: None, is_archive_in_lookaside_cache=lambda archive_path: False, upload_to_lookaside_cache=lambda archive, pkg_tool: None, download_upstream_archive=lambda: "the-archive", ) pr = flexmock(url="https://example.com/pull/1") flexmock(DistGit).should_receive("existing_pr").and_return(pr) pc = get_local_package_config(str(upstream_path)) up_lp = LocalProject(working_dir=upstream_path) c = get_test_config() api = PackitAPI(c, pc, up_lp) api._dg = DistGit(c, pc) api._dg._local_project = LocalProject(working_dir=dist_git_path) with cwd(upstream_path): assert pr == api.sync_release( dist_git_branch="main", use_local_content=False, version="179", force_new_sources=False, create_pr=True, )
def test_basic_local_update(upstream_n_distgit, mock_remote_functionality_upstream): """ basic propose-update test: mock remote API, use local upstream and dist-git """ u, d = upstream_n_distgit with cwd(u): c = get_test_config() pc = get_local_package_config(str(u)) pc.upstream_project_url = str(u) pc.downstream_project_url = str(d) up_lp = LocalProject(path_or_url=str(u)) api = PackitAPI(c, pc, up_lp) api.sync_release("master", "0.1.0") assert (d / TARBALL_NAME).is_file() spec = get_specfile(str(d / "beer.spec")) assert spec.get_version() == "0.1.0"
def run(self) -> TaskResults: local_project = LocalProject( git_project=self.project, working_dir=self.service_config.command_handler_work_dir, ) api = PackitAPI( config=self.service_config, # job_config and package_config are the same for PackitAPI # and we want to use job_config since people can override things in there package_config=self.job_config, upstream_local_project=local_project, ) user_can_merge_pr = self.project.can_merge_pr(self.data.user_login) if not (user_can_merge_pr or self.data.user_login in self.service_config.admins): self.project.issue_comment(self.db_trigger.issue_id, PERMISSIONS_ERROR_WRITE_OR_ADMIN) return TaskResults( success=True, details={"msg": PERMISSIONS_ERROR_WRITE_OR_ADMIN}) if not self.data.tag_name: msg = ( "There was an error while proposing a new update for the Fedora package: " "no upstream release found.") self.project.issue_comment(self.db_trigger.issue_id, msg) return TaskResults(success=False, details={"msg": "Propose update failed"}) sync_failed = False for branch in self.dist_git_branches_to_sync: msg = ( f"for the Fedora package `{self.job_config.downstream_package_name}`" f"with the tag `{self.data.tag_name}` in the `{branch}` branch.\n" ) try: new_pr = api.sync_release(dist_git_branch=branch, version=self.data.tag_name, create_pr=True) msg = f"Packit-as-a-Service proposed [a new update]({new_pr.url}) {msg}" self.project.issue_comment(self.db_trigger.issue_id, msg) except PackitException as ex: msg = f"There was an error while proposing a new update {msg} Traceback is: `{ex}`" self.project.issue_comment(self.db_trigger.issue_id, msg) logger.error(f"Error while running a build: {ex}") sync_failed = True if sync_failed: return TaskResults(success=False, details={"msg": "Propose update failed"}) # Close issue if propose-update was successful in all branches self.project.issue_close(self.db_trigger.issue_id) return TaskResults(success=True, details={})
class GithubReleaseHandler(JobHandler): name = JobType.propose_downstream triggers = [JobTriggerType.release] def run(self) -> HandlerResults: """ Sync the upstream release to dist-git as a pull request. """ version = self.event["release"]["tag_name"] self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir ) self.api = PackitAPI(self.config, self.package_config, self.local_project) self.api.sync_release( dist_git_branch=self.job.metadata.get("dist-git-branch", "master"), version=version, ) return HandlerResults(success=True, details={})
class ProposeUpdate(PackitUnittestOgr): def setUp(self): super().setUp() self.api = PackitAPI(config=self.conf, package_config=self.pc, upstream_local_project=self.lp) self.api._up = self.upstream self.api._dg = self.dg # Do not upload package, because no credentials given in CI flexmock(self.api).should_receive("_handle_sources").and_return(None) flexmock(self.api.dg).should_receive("push").and_return(None) flexmock(git.HEAD).should_receive("commit").and_return( "hash-of-some-commit") self.set_git_user() def test_propose_update(self): # change specfile little bit to have there some change specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") with open(specfile_location, "r") as myfile: filedata = myfile.read() # Patch the specfile with new version version_increase = "0.0.0" for line in filedata.splitlines(): if "Version:" in line: version = line.rsplit(" ", 1)[1] v1, v2, v3 = version.split(".") version_increase = ".".join([v1, str(int(v2) + 1), v3]) filedata = filedata.replace(version, version_increase) break with open(specfile_location, "w") as myfile: myfile.write(filedata) check_output( f"cd {self.lp.working_dir};" f"git commit -m 'test change' python-ogr.spec;" f"git tag -a {version_increase} -m 'my version {version_increase}'", shell=True, ) self.api.sync_release("master")
def test_update_on_cockpit_ostree(cockpit_ostree): upstream_path, dist_git_path = cockpit_ostree def mocked_new_sources(sources=None): if not Path(sources).is_file(): raise RuntimeError("archive does not exist") flexmock(FedPKG, new_sources=mocked_new_sources) flexmock(PackitAPI, init_kerberos_ticket=lambda: None) flexmock( DistGit, push_to_fork=lambda *args, **kwargs: None, is_archive_in_lookaside_cache=lambda archive_path: False, upload_to_lookaside_cache=lambda path: None, download_upstream_archive=lambda: "the-archive", ) flexmock( PackitAPI, push_and_create_pr=lambda pr_title, pr_description, dist_git_branch: None, ) pc = get_local_package_config(str(upstream_path)) up_lp = LocalProject(working_dir=upstream_path) c = get_test_config() api = PackitAPI(c, pc, up_lp) api._dg = DistGit(c, pc) api._dg._local_project = LocalProject(working_dir=dist_git_path) with cwd(upstream_path): api.sync_release( dist_git_branch="main", use_local_content=False, version="179", force_new_sources=False, create_pr=True, )
class GithubReleaseHandler(AbstractGithubJobHandler): name = JobType.propose_downstream triggers = [JobTriggerType.release] event: ReleaseEvent def __init__( self, config: ServiceConfig, job: JobConfig, release_event: ReleaseEvent ): super().__init__(config=config, job=job, event=release_event) self.project: GitProject = release_event.get_project() self.package_config: PackageConfig = get_package_config_from_repo( self.project, release_event.tag_name ) if not self.package_config: raise ValueError(f"No config file found in {self.project.full_repo_name}") self.package_config.upstream_project_url = release_event.project_url def run(self) -> HandlerResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir ) self.api = PackitAPI(self.config, self.package_config, self.local_project) # create_pr is set to False. # Each upstream project decides # if creates PR or pushes directly into dist-git directly from packit.yaml file. self.api.sync_release( dist_git_branch=self.job.metadata.get("dist-git-branch", "master"), version=self.event.tag_name, create_pr=False, ) return HandlerResults(success=True, details={})
def test_basic_local_update(upstream_n_distgit, mock_remote_functionality_upstream): """ basic propose-update test: mock remote API, use local upstream and dist-git """ u, d = upstream_n_distgit with cwd(u): c = get_test_config() pc = get_local_package_config(str(u)) pc.upstream_project_url = str(u) pc.dist_git_clone_path = str(d) up_lp = LocalProject(working_dir=str(u)) api = PackitAPI(c, pc, up_lp) api.sync_release("master", "0.1.0") assert (d / TARBALL_NAME).is_file() spec = get_specfile(str(d / "beer.spec")) assert spec.get_version() == "0.1.0" assert (d / "README.packit").is_file() # assert that we have changelog entries for both versions changelog = "\n".join(spec.spec_content.section("%changelog")) assert "0.0.0" in changelog assert "0.1.0" in changelog
class ProposeDownstreamHandler(JobHandler): type = JobType.propose_downstream triggers = [TheJobTriggerType.release] task_name = TaskName.propose_downstream def __init__( self, package_config: PackageConfig, job_config: JobConfig, data: EventData, task: Task, ): super().__init__( package_config=package_config, job_config=job_config, data=data, ) self.task = task def run(self) -> TaskResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.service_config.command_handler_work_dir, ) self.api = PackitAPI(self.service_config, self.job_config, self.local_project) errors = {} for branch in get_branches(*self.job_config.metadata.dist_git_branches, default="master"): try: self.api.sync_release(dist_git_branch=branch, version=self.data.tag_name) except Exception as ex: # the archive has not been uploaded to PyPI yet if FILE_DOWNLOAD_FAILURE in str(ex): # retry for the archive to become available logger.info( f"We were not able to download the archive: {ex}") # when the task hits max_retries, it raises MaxRetriesExceededError # and the error handling code would be never executed retries = self.task.request.retries if retries < RETRY_LIMIT: logger.info(f"Retrying for the {retries + 1}. time...") self.task.retry(exc=ex, countdown=15 * 2**retries) sentry_integration.send_to_sentry(ex) errors[branch] = str(ex) if errors: branch_errors = "" for branch, err in sorted( errors.items(), key=lambda branch_error: branch_error[0]): err_without_new_lines = err.replace("\n", " ") branch_errors += f"| `{branch}` | `{err_without_new_lines}` |\n" body_msg = ( f"Packit failed on creating pull-requests in dist-git:\n\n" f"| dist-git branch | error |\n" f"| --------------- | ----- |\n" f"{branch_errors}\n\n" "You can re-trigger the update by adding `/packit propose-update`" " to the issue comment.\n") self.project.create_issue( title= f"[packit] Propose update failed for release {self.data.tag_name}", body=body_msg, ) return TaskResults( success=False, details={ "msg": "Propose update failed.", "errors": errors }, ) return TaskResults(success=True, details={})
class GitHubIssueCommentProposeUpdateHandler(CommentActionHandler): """ Handler for issue comment `/packit propose-update` """ name = CommentAction.propose_update event: IssueCommentEvent def __init__(self, config: ServiceConfig, event: IssueCommentEvent): super().__init__(config=config, event=event) self.config = config self.event = event self.project = self.event.get_project() # Get the latest tag release self.event.tag_name = self.project.get_latest_release().tag_name self.package_config: PackageConfig = get_package_config_from_repo( self.project, self.event.tag_name) if not self.package_config: raise ValueError( f"No config file found in {self.project.full_repo_name}") self.package_config.upstream_project_url = event.project_url @property def dist_git_branches_to_sync(self) -> List[str]: """ Get the dist-git branches to sync to with the aliases expansion. :return: list of dist-git branches """ configured_branches = [ job.metadata.get("dist-git-branch") for job in self.package_config.jobs if job.job == JobType.propose_downstream ] if configured_branches: return list(get_branches(*configured_branches)) return [] def run(self) -> HandlerResults: self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir) self.api = PackitAPI(self.config, self.package_config, self.local_project) collaborators = self.project.who_can_merge_pr() if self.event.github_login not in collaborators | self.config.admins: msg = "Only collaborators can trigger Packit-as-a-Service" self.project.issue_comment(self.event.issue_id, msg) return HandlerResults(success=True, details={"msg": msg}) sync_failed = False for branch in self.dist_git_branches_to_sync: msg = ( f"a new update for the Fedora package " f"`{self.package_config.downstream_package_name}`" f"with the tag `{self.event.tag_name}` in the `{branch}` branch.\n" ) try: self.api.sync_release(dist_git_branch=branch, version=self.event.tag_name) msg = f"Packit-as-a-Service proposed {msg}" self.project.issue_comment(self.event.issue_id, msg) except PackitException as ex: msg = f"There was an error while proposing {msg} Traceback is: `{ex}`" self.project.issue_comment(self.event.issue_id, msg) logger.error(f"error while running a build: {ex}") sync_failed = True if sync_failed: return HandlerResults(success=False, details={}) # Close issue if propose-update was successful in all branches self.project.issue_close(self.event.issue_id) return HandlerResults(success=True, details={})
class GithubReleaseHandler(AbstractGithubJobHandler): name = JobType.propose_downstream triggers = [JobTriggerType.release] event: ReleaseEvent def __init__(self, config: ServiceConfig, job: JobConfig, release_event: ReleaseEvent): super().__init__(config=config, job=job, event=release_event) self.project: GitProject = release_event.get_project() self.package_config: PackageConfig = self.get_package_config_from_repo( self.project, release_event.tag_name) self.package_config.upstream_project_url = release_event.project_url def run(self) -> HandlerResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir) self.api = PackitAPI(self.config, self.package_config, self.local_project) errors = {} for branch in get_branches( self.job.metadata.get("dist-git-branch", "master")): try: self.api.sync_release(dist_git_branch=branch, version=self.event.tag_name) except Exception as ex: sentry_integration.send_to_sentry(ex) errors[branch] = str(ex) if errors: branch_errors = "" for branch, err in sorted( errors.items(), key=lambda branch_error: branch_error[0]): err_without_new_lines = err.replace("\n", " ") branch_errors += f"| `{branch}` | `{err_without_new_lines}` |\n" body_msg = ( f"Packit failed on creating pull-requests in dist-git:\n\n" f"| dist-git branch | error |\n" f"| --------------- | ----- |\n" f"{branch_errors}\n\n" "You can re-trigger the update by adding `/packit propose-update`" " to the issue comment.\n") self.project.create_issue( title= f"[packit] Propose update failed for release {self.event.tag_name}", body=body_msg, ) return HandlerResults( success=False, details={ "msg": "Propose update failed.", "errors": errors }, ) return HandlerResults(success=True, details={})
class ProposeUpdate(PackitUnittestOgr): @classmethod def _feature_id(cls): ogr_module = importlib.import_module("ogr") if "parent" in inspect.getsource( ogr_module.services.pagure.PagureProject.is_fork.fget): cls.variant = "ogr_old_fork" def setUp(self): self._feature_id() super().setUp() self.api = PackitAPI(config=self.conf, package_config=self.pc, upstream_local_project=self.lp) self.api._up = self.upstream self.api._dg = self.dg self.set_git_user() def check_version_increase(self): # change specfile little bit to have there some change specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") with open(specfile_location, "r") as myfile: filedata = myfile.read() # Patch the specfile with new version version_increase = "0.0.0" for line in filedata.splitlines(): if "Version:" in line: version = line.rsplit(" ", 1)[1] v1, v2, v3 = version.split(".") version_increase = ".".join([v1, str(int(v2) + 1), v3]) filedata = filedata.replace(version, version_increase) break with open(specfile_location, "w") as myfile: myfile.write(filedata) check_output( f"cd {self.lp.working_dir};" f"git commit -m 'test change' python-ogr.spec;" f"git tag -a {version_increase} -m 'my version {version_increase}'", shell=True, ) self.api.sync_release("master") @unittest.skip( reason="https://github.com/packit-service/packit/issues/562 and #561") def test_comment_in_spec(self): """ change specfile little bit to have there some change, do not increase version """ specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") version_increase = "10.0.0" with open(specfile_location, "a") as myfile: myfile.write("\n# comment\n") check_output( f"cd {self.lp.working_dir};" f"git commit -m 'test change' python-ogr.spec;" f"git tag -a {version_increase} -m 'my version {version_increase}'", shell=True, ) self.api.sync_release("master") # @unittest.skipIf( # hasattr(rebasehelper, "VERSION") # and int(rebasehelper.VERSION.split(".")[1]) >= 19, # "Older version of rebasehelper raised exception", # ) @unittest.skip( reason="https://github.com/packit-service/packit/issues/562 and #561") def test_version_change_exception(self): """ check if it raises exception, because sources are not uploaded in distgit Downgrade rebasehelper to version < 0.19.0 """ self.assertRaises(RebaseHelperError, self.check_version_increase) # @unittest.skipUnless( # hasattr(rebasehelper, "VERSION") # and int(rebasehelper.VERSION.split(".")[1]) >= 19, # "New version of rebasehelper works without raised exception", # ) @unittest.skip(reason="https://github.com/packit-service/packit/issues/558" ) def test_version_change_new_rebaseheler(self): """ check if it not raises exception, because sources are not uploaded in distgit """ self.check_version_increase() @unittest.skip( reason="https://github.com/packit-service/packit/issues/562 and #561") def test_version_change_mocked(self): """ version is not not uploaded, so skip in this test """ flexmock(self.api).should_receive("_handle_sources").and_return(None) self.check_version_increase()
class ProposeUpdate(PackitUnittestOgr): def setUp(self): if (hasattr(rebasehelper, "VERSION") and int(rebasehelper.VERSION.split(".")[1]) >= 19): DataMiner.key = "rebase-helper>=0.19" else: DataMiner.key = "rebase-helper<0.19" DataMiner.data_type = DataTypes.Dict super().setUp() self.api = PackitAPI(config=self.conf, package_config=self.pc, upstream_local_project=self.lp) self.api._up = self.upstream self.api._dg = self.dg self.set_git_user() def check_version_increase(self): # change specfile little bit to have there some change specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") with open(specfile_location, "r") as myfile: filedata = myfile.read() # Patch the specfile with new version version_increase = "0.0.0" for line in filedata.splitlines(): if "Version:" in line: version = line.rsplit(" ", 1)[1] v1, v2, v3 = version.split(".") version_increase = ".".join([v1, str(int(v2) + 1), v3]) filedata = filedata.replace(version, version_increase) break with open(specfile_location, "w") as myfile: myfile.write(filedata) check_output( f"cd {self.lp.working_dir};" f"git commit -m 'test change' python-ogr.spec;" f"git tag -a {version_increase} -m 'my version {version_increase}'", shell=True, ) self.api.sync_release("master") def test_comment_in_spec(self): """ change specfile little bit to have there some change, do not increase version """ specfile_location = os.path.join(self.lp.working_dir, "python-ogr.spec") version_increase = "10.0.0" with open(specfile_location, "a") as myfile: myfile.write("\n# comment\n") check_output( f"cd {self.lp.working_dir};" f"git commit -m 'test change' python-ogr.spec;" f"git tag -a {version_increase} -m 'my version {version_increase}'", shell=True, ) self.api.sync_release("master") @unittest.skipIf( hasattr(rebasehelper, "VERSION") and int(rebasehelper.VERSION.split(".")[1]) >= 19, "Older version of rebasehelper raised exception", ) def test_version_change_exception(self): """ check if it raises exception, because sources are not uploaded in distgit Downgrade rebasehelper to version < 0.19.0 """ self.assertRaises(RebaseHelperError, self.check_version_increase) @unittest.skipUnless( hasattr(rebasehelper, "VERSION") and int(rebasehelper.VERSION.split(".")[1]) >= 19, "New version of rebasehelper works without raised exception", ) def test_version_change_new_rebaseheler(self): """ check if it not raises exception, because sources are not uploaded in distgit """ self.check_version_increase() def test_version_change_mocked(self): """ version is not not uploaded, so skip in this test """ flexmock(self.api).should_receive("_handle_sources").and_return(None) self.check_version_increase()
class GitHubIssueCommentProposeUpdateHandler(CommentActionHandler, GithubPackageConfigGetter): """ Handler for issue comment `/packit propose-update` """ type = CommentAction.propose_update triggers = [TheJobTriggerType.issue_comment] event: IssueCommentEvent def __init__(self, config: ServiceConfig, event: IssueCommentEvent): super().__init__(config=config, event=event) self.config = config self.event = event self.project = self.event.get_project() self.package_config: PackageConfig = self.get_package_config_from_repo( self.project, self.event.tag_name) if not self.package_config: raise ValueError( f"No config file found in {self.project.full_repo_name}") self.package_config.upstream_project_url = event.project_url @property def dist_git_branches_to_sync(self) -> Set[str]: """ Get the dist-git branches to sync to with the aliases expansion. :return: list of dist-git branches """ configured_branches = set() for job in self.package_config.jobs: if job.type == JobType.propose_downstream: configured_branches.update(job.metadata.dist_git_branches) if configured_branches: return get_branches(*configured_branches) return set() def run(self) -> HandlerResults: self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir) self.api = PackitAPI(self.config, self.package_config, self.local_project) collaborators = self.project.who_can_merge_pr() if self.event.github_login not in collaborators | self.config.admins: self.project.issue_comment(self.event.issue_id, PERMISSIONS_ERROR_WRITE_OR_ADMIN) return HandlerResults( success=True, details={"msg": PERMISSIONS_ERROR_WRITE_OR_ADMIN}) if not self.event.tag_name: msg = ( "There was an error while proposing a new update for the Fedora package: " "no upstream release found.") self.project.issue_comment(self.event.issue_id, msg) return HandlerResults(success=False, details={"msg": "Propose update failed"}) sync_failed = False for branch in self.dist_git_branches_to_sync: msg = ( f"for the Fedora package `{self.package_config.downstream_package_name}`" f"with the tag `{self.event.tag_name}` in the `{branch}` branch.\n" ) try: new_pr = self.api.sync_release(dist_git_branch=branch, version=self.event.tag_name, create_pr=True) msg = f"Packit-as-a-Service proposed [a new update]({new_pr.url}) {msg}" self.project.issue_comment(self.event.issue_id, msg) except PackitException as ex: msg = f"There was an error while proposing a new update {msg} Traceback is: `{ex}`" self.project.issue_comment(self.event.issue_id, msg) logger.error(f"error while running a build: {ex}") sync_failed = True if sync_failed: return HandlerResults(success=False, details={"msg": "Propose update failed"}) # Close issue if propose-update was successful in all branches self.project.issue_close(self.event.issue_id) return HandlerResults(success=True, details={})
class ProposeDownstreamHandler(JobHandler): task_name = TaskName.propose_downstream def __init__( self, package_config: PackageConfig, job_config: JobConfig, data: EventData, task: Task, ): super().__init__( package_config=package_config, job_config=job_config, data=data, ) self.task = task def run(self) -> TaskResults: """ Sync the upstream release to dist-git as a pull request. """ self.local_project = LocalProject( git_project=self.project, working_dir=self.service_config.command_handler_work_dir, ) self.api = PackitAPI(self.service_config, self.job_config, self.local_project) errors = {} default_dg_branch = self.api.dg.local_project.git_project.default_branch for branch in get_branches(*self.job_config.metadata.dist_git_branches, default=default_dg_branch): try: self.api.sync_release(dist_git_branch=branch, tag=self.data.tag_name) except Exception as ex: # the archive has not been uploaded to PyPI yet if FILE_DOWNLOAD_FAILURE in str(ex): # retry for the archive to become available logger.info( f"We were not able to download the archive: {ex}") # when the task hits max_retries, it raises MaxRetriesExceededError # and the error handling code would be never executed retries = self.task.request.retries if retries < int( getenv("CELERY_RETRY_LIMIT", DEFAULT_RETRY_LIMIT)): logger.info(f"Retrying for the {retries + 1}. time...") # throw=False so that exception is not raised and task # is not retried also automatically self.task.retry(exc=ex, countdown=15 * 2**retries, throw=False) return TaskResults( success=False, details={ "msg": "Task was retried, we were not able to download the archive." }, ) sentry_integration.send_to_sentry(ex) errors[branch] = str(ex) if errors: branch_errors = "" for branch, err in sorted( errors.items(), key=lambda branch_error: branch_error[0]): err_without_new_lines = err.replace("\n", " ") branch_errors += f"| `{branch}` | `{err_without_new_lines}` |\n" msg_retrigger = MSG_RETRIGGER.format(job="update", command="propose-downstream", place="issue") body_msg = ( f"Packit failed on creating pull-requests in dist-git:\n\n" f"| dist-git branch | error |\n" f"| --------------- | ----- |\n" f"{branch_errors}\n\n" f"{msg_retrigger}\n") self.project.create_issue( title= f"[packit] Propose downstream failed for release {self.data.tag_name}", body=body_msg, ) return TaskResults( success=False, details={ "msg": "Propose downstream failed.", "errors": errors }, ) return TaskResults(success=True, details={})
class GitHubIssueCommentProposeUpdateHandler(CommentActionHandler): """ Issue handler for comment `/packit propose-update` """ name = CommentAction.propose_update event: IssueCommentEvent def __init__(self, config: Config, event: IssueCommentEvent): super().__init__(config=config, event=event) self.config = config self.event = event self.project: GitProject = self.github_service.get_project( repo=event.base_repo_name, namespace=event.base_repo_namespace) # Get the latest tag release self.event.tag_name = self.project.get_latest_release().tag_name self.package_config: PackageConfig = get_package_config_from_repo( self.project, self.event.tag_name) self.package_config.upstream_project_url = event.https_url def get_build_metadata_for_build(self) -> List[str]: """ Check if there are propose-update defined :return: JobConfig or Empty list """ return [ job.metadata.get("dist-git-branch") for job in self.package_config.jobs if job.job == JobType.propose_downstream ] def run(self) -> HandlerResults: self.local_project = LocalProject( git_project=self.project, working_dir=self.config.command_handler_work_dir) self.api = PackitAPI(self.config, self.package_config, self.local_project) collaborators = self.project.who_can_merge_pr() if self.event.github_login not in collaborators: msg = "Only collaborators can trigger Packit-as-a-Service" self.project.issue_comment(self.event.issue_id, msg) return HandlerResults(success=False, details={"msg": msg}) branches = self.get_build_metadata_for_build() sync_failed = False for brn in branches: msg = ( f"a new update for the Fedora package " f"`{self.package_config.downstream_package_name}`" f"with the tag `{self.event.tag_name}` in the `{brn}` branch.\n" ) try: self.api.sync_release(dist_git_branch=brn, version=self.event.tag_name) msg = f"Packit-as-a-Service proposed {msg}" self.project.issue_comment(self.event.issue_id, msg) except PackitException as ex: msg = f"There was an error while proposing {msg} Traceback is: `{ex}`" self.project.issue_comment(self.event.issue_id, msg) logger.error(f"error while running a build: {ex}") sync_failed = True if sync_failed: return HandlerResults(success=False, details={}) # Close issue if propose-update was successful in all branches self.project.issue_close(self.event.issue_id) return HandlerResults(success=True, details={})