class ReleaseBot: def __init__(self, configuration): self.conf = configuration url = f'https://github.com/{self.conf.repository_owner}/{self.conf.repository_name}.git' self.git = Git(url, self.conf) self.github = Github(configuration, self.git) self.pypi = PyPi(configuration, self.git) self.fedora = Fedora(configuration) self.logger = configuration.logger # FIXME: it's cumbersome to work with these dicts - it's unclear how the content changes; # get rid of them and replace them with individual variables self.new_release = {} self.new_pr = {} def cleanup(self): if 'tempdir' in self.new_release: self.new_release['tempdir'].cleanup() self.new_release = {} self.new_pr = {} self.github.comment = [] self.fedora.progress_log = [] self.git.cleanup() def load_release_conf(self): """ Updates new_release with latest release-conf.yaml from repository :return: """ # load release configuration from release-conf.yaml in repository conf = self.github.get_configuration() release_conf = self.conf.load_release_conf(conf) self.new_release.update(release_conf) def find_open_release_issues(self): """ Looks for opened release issues on github :return: True on found, False if not found """ cursor = '' release_issues = {} while True: edges = self.github.walk_through_open_issues(start=cursor, direction='before') if not edges: self.logger.debug(f'No more open issues found') break else: for edge in reversed(edges): cursor = edge['cursor'] match = re.match(r'(.+) release', edge['node']['title'].lower()) if match: version = match[1].strip() if validate(version): if edge['node']['authorAssociation'] in ['MEMBER', 'OWNER', 'COLLABORATOR']: release_issues[version] = edge['node'] self.logger.info(f'Found new release issue with version: {version}') else: self.logger.warning( f"Author association {edge['node']['authorAssociation']!r} " f"not in ['MEMBER', 'OWNER', 'COLLABORATOR']") else: self.logger.warning(f"{version!r} is not a valid version") if len(release_issues) > 1: msg = f'Multiple release issues are open {release_issues}, please reduce them to one' self.logger.error(msg) return False if len(release_issues) == 1: for version, node in release_issues.items(): self.new_pr = {'version': version, 'issue_id': node['id'], 'issue_number': node['number'], 'labels': self.new_release.get('labels')} return True else: return False def find_newest_release_pull_request(self): """ Find newest merged release PR :return: bool, whether PR was found """ cursor = '' while True: edges = self.github.walk_through_prs(start=cursor, direction='before', closed=True) if not edges: self.logger.debug(f'No merged release PR found') return False for edge in reversed(edges): cursor = edge['cursor'] match = re.match(r'(.+) release', edge['node']['title'].lower()) if match and validate(match[1]): merge_commit = edge['node']['mergeCommit'] print('thisssssssssss') self.logger.warn(f"merge_commit") logging.info(merge_commit) self.logger.info(f"Found merged release PR with version {match[1]}, " f"commit id: {merge_commit['oid']}") new_release = {'version': match[1], 'commitish': merge_commit['oid'], 'pr_id': edge['node']['id'], 'author_name': merge_commit['author']['name'], 'author_email': merge_commit['author']['email']} self.new_release.update(new_release) return True def make_release_pull_request(self): """ Makes release pull request and handles outcome :return: whether making PR was successful """ def pr_handler(success): """ Handler for the outcome of making a PR :param success: whether making PR was successful :return: """ result = 'made' if success else 'failed to make' msg = f"I just {result} a PR request for a release version {self.new_pr['version']}" level = logging.INFO if success else logging.ERROR self.logger.log(level, msg) if success: msg += f"\n Here's a [link to the PR]({self.new_pr['pr_url']})" comment_backup = self.github.comment.copy() self.github.comment = [msg] self.github.add_comment(self.new_pr['issue_id']) self.github.comment = comment_backup if success: self.github.close_issue(self.new_pr['issue_number']) latest_gh_str = self.github.latest_release() self.new_pr['previous_version'] = latest_gh_str if Version.coerce(latest_gh_str) >= Version.coerce(self.new_pr['version']): msg = f"Version ({latest_gh_str}) is already released and this issue is ignored." self.logger.warning(msg) return False msg = f"Making a new PR for release of version {self.new_pr['version']} based on an issue." self.logger.info(msg) try: self.new_pr['repo'] = self.git if not self.new_pr['repo']: raise ReleaseException("Couldn't clone repository!") if self.github.make_release_pr(self.new_pr): pr_handler(success=True) return True except ReleaseException: pr_handler(success=False) raise return False def make_new_github_release(self): def release_handler(success): result = "released" if success else "failed to release" msg = f"I just {result} version {self.new_release['version']} on Github" level = logging.INFO if success else logging.ERROR self.logger.log(level, msg) self.github.comment.append(msg) try: latest_release = self.github.latest_release() except ReleaseException as exc: raise ReleaseException(f"Failed getting latest Github release (zip).\n{exc}") if Version.coerce(latest_release) >= Version.coerce(self.new_release['version']): self.logger.info(f"{self.new_release['version']} yoooooooooooooooooo") self.logger.info( f"{self.new_release['version']} has already been released on Github") else: try: released, self.new_release = self.github.make_new_release(self.new_release) if released: release_handler(success=True) except ReleaseException: release_handler(success=False) raise self.github.update_changelog(self.new_release['version']) return self.new_release def make_new_pypi_release(self): def release_handler(success): result = "released" if success else "failed to release" msg = f"I just {result} version {self.new_release['version']} on PyPI" level = logging.INFO if success else logging.ERROR self.logger.log(level, msg) self.github.comment.append(msg) latest_pypi = self.pypi.latest_version() if Version.coerce(latest_pypi) >= Version.coerce(self.new_release['version']): self.logger.info(f"{self.new_release['version']} has already been released on PyPi") return False self.git.fetch_tags() self.git.checkout(self.new_release['version']) try: self.pypi.release(self.new_release) release_handler(success=True) except ReleaseException: release_handler(success=False) raise return True def make_new_fedora_release(self): if not self.new_release.get('fedora'): self.logger.debug('Skipping Fedora release') return self.logger.info("Triggering Fedora release") def release_handler(success): result = "released" if success else "failed to release" msg = f"I just {result} on Fedora" builds = ', '.join(self.fedora.builds) bodhi_update_url = "https://bodhi.fedoraproject.org/updates/new" if builds: msg += f", successfully built for branches: {builds}." msg += f" Follow this link to create bodhi update(s): {bodhi_update_url}" level = logging.INFO if success else logging.ERROR self.logger.log(level, msg) self.github.comment.append(msg) try: name, email = self.github.get_user_contact() self.new_release['commit_name'] = name self.new_release['commit_email'] = email success_ = self.fedora.release(self.new_release) release_handler(success_) except ReleaseException: release_handler(success=False) raise def run(self): # self.logger.info(f"release-bot v{configuration.version} reporting for duty!") self.logger.info(f"release-bot v{configuration.version} duty!") try: while True: self.git.pull() try: self.load_release_conf() if self.find_newest_release_pull_request(): self.make_new_github_release() # Try to do PyPi release regardless whether we just did github release # for case that in previous iteration (of the 'while True' loop) # we succeeded with github release, but failed with PyPi release if self.make_new_pypi_release(): # There's no way how to tell whether there's already such a fedora 'release' # so try to do it only when we just did PyPi release self.make_new_fedora_release() if self.new_release.get('trigger_on_issue') and self.find_open_release_issues(): if self.new_release.get('labels') is not None: self.github.put_labels_on_issue(self.new_pr['issue_number'], self.new_release.get('labels')) self.make_release_pull_request() except ReleaseException as exc: self.logger.error(exc) self.github.add_comment(self.new_release.get('pr_id')) self.logger.debug(f"Done. Going to sleep for {self.conf.refresh_interval}s") time.sleep(self.conf.refresh_interval) finally: self.cleanup()
from bitbucket import Bitbucket from cli_args import get_cli_args from git import Git from github import Github if __name__ == '__main__': args = get_cli_args() bitbucket = Bitbucket(args.bitbucket_login, args.bitbucket_password) github = Github(args.github_login, args.github_password) git = Git(args.storage_path) migrated_repositories = bitbucket.get_repositories() for source_repository in migrated_repositories: print(source_repository) if github.repository_exists(source_repository): continue destination_repository = github.create_repository(source_repository) cloned_source_repository = git.download(source_repository) git.upload(cloned_source_repository, destination_repository) git.cleanup(cloned_source_repository)