async def release(*, github_access_token, repo_info, new_version, branch=None, commit_hash=None): """ Run a release Args: github_access_token (str): The github access token repo_info (RepoInfo): RepoInfo for a repo new_version (str): The version of the new release branch (str): The branch to initialize the release from commit_hash (str): Commit hash to cherry pick in case of a hot fix """ await validate_dependencies() async with init_working_dir(github_access_token, repo_info.repo_url, branch=branch) as working_dir: default_branch = await get_default_branch(working_dir) await check_call(["git", "checkout", "-qb", "release-candidate"], cwd=working_dir) if commit_hash: try: await check_call(["git", "cherry-pick", commit_hash], cwd=working_dir) except CalledProcessError as ex: raise ReleaseException( f"Cherry pick failed for the given hash {commit_hash}" ) from ex old_version = await update_version( repo_info=repo_info, new_version=new_version, working_dir=working_dir, readonly=False, ) if parse_version(old_version) >= parse_version(new_version): raise ReleaseException( f"old version is {old_version} but the new version {new_version} is not newer" ) base_branch = "release-candidate" if commit_hash else default_branch await verify_new_commits(old_version, base_branch=base_branch, root=working_dir) await update_release_notes(old_version, new_version, base_branch=base_branch, root=working_dir) await build_release(root=working_dir) return await generate_release_pr( github_access_token=github_access_token, repo_url=repo_info.repo_url, old_version=old_version, new_version=new_version, base_branch=base_branch, root=working_dir, )
async def release(github_access_token, repo_url, new_version, branch=None, commit_hash=None): """ Run a release Args: github_access_token (str): The github access token repo_url (str): URL for a repo new_version (str): The version of the new release branch (str): The branch to initialize the release from commit_hash (str): Commit hash to cherry pick in case of a hot fix """ await validate_dependencies() async with init_working_dir(github_access_token, repo_url, branch=branch) as working_dir: await check_call(["git", "checkout", "-qb", "release-candidate"], cwd=working_dir) if commit_hash: try: await check_call(["git", "cherry-pick", commit_hash], cwd=working_dir) except CalledProcessError: raise ReleaseException( f"Cherry pick failed for the given hash {commit_hash}") old_version = update_version(new_version, working_dir=working_dir) if parse_version(old_version) >= parse_version(new_version): raise ReleaseException( "old version is {old} but the new version {new} is not newer". format( old=old_version, new=new_version, )) base_branch = "release-candidate" if commit_hash else "master" await verify_new_commits(old_version, base_branch=base_branch, root=working_dir) await update_release_notes(old_version, new_version, base_branch=base_branch, root=working_dir) await build_release(root=working_dir) await generate_release_pr( github_access_token=github_access_token, repo_url=repo_url, old_version=old_version, new_version=new_version, base_branch=base_branch, root=working_dir, ) print(f"version {old_version} has been updated to {new_version}") print("Go tell engineers to check their work. PR is on the repo.") print("After they are done, run the finish_release.py script.")
def release(github_access_token, repo_url, new_version): """ Run a release Args: github_access_token (str): The github access token repo_url (str): URL for a repo new_version (str): The version of the new release """ validate_dependencies() with init_working_dir(github_access_token, repo_url): check_call(["git", "checkout", "-qb", "release-candidate"]) old_version = update_version(new_version) if parse_version(old_version) >= parse_version(new_version): raise ReleaseException("old version is {old} but the new version {new} is not newer".format( old=old_version, new=new_version, )) verify_new_commits(old_version) update_release_notes(old_version, new_version) build_release() generate_release_pr(github_access_token, repo_url, old_version, new_version) print("version {old_version} has been updated to {new_version}".format( old_version=old_version, new_version=new_version, )) print("Go tell engineers to check their work. PR is on the repo.") print("After they are done, run the finish_release.py script.")
async def get_release_pr(*, github_access_token, org, repo): """ Look up the pull request information for a release, or return None if it doesn't exist Args: github_access_token (str): The github access token org (str): The github organization (eg mitodl) repo (str): The github repository (eg micromasters) Returns: ReleasePR: The information about the release pull request, or None if there is no release PR in progress """ pr = await get_pull_request( github_access_token=github_access_token, org=org, repo=repo, branch='release-candidate', ) if pr is None: return None title = pr['title'] match = re.match(r'^Release (?P<version>\d+\.\d+\.\d+)$', title) if not match: raise ReleaseException("Release PR title has an unexpected format") version = match.group('version') return ReleasePR( version=version, body=pr['body'], url=pr['html_url'], )
async def get_unchecked_authors(*, github_access_token, org, repo): """ Returns list of authors who have not yet checked off their checkboxes Args: github_access_token (str): The github access token org (str): The github organization (eg mitodl) repo (str): The github repository (eg micromasters) Returns: set[str]: A set of github usernames """ release_pr = await get_release_pr( github_access_token=github_access_token, org=org, repo=repo, ) if not release_pr: raise ReleaseException("No release PR found") body = release_pr.body commits = parse_checkmarks(body) return { commit['author_name'] for commit in commits if not commit['checked'] }
async def release_command(self, command_args): """ Start a new release and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info version = command_args.args[0] repo_url = repo_info.repo_url channel_id = repo_info.channel_id org, repo = get_org_and_repo(repo_url) pr = get_release_pr(self.github_access_token, org, repo) if pr: raise ReleaseException( "A release is already in progress: {}".format(pr.url)) release( github_access_token=self.github_access_token, repo_url=repo_url, new_version=version, ) await self.say( channel_id=channel_id, text= "Behold, my new evil scheme - release {version} for {project}! Now deploying to RC..." .format( version=version, project=repo_info.name, ), ) await wait_for_deploy( github_access_token=self.github_access_token, repo_url=repo_url, hash_url=repo_info.rc_hash_url, watch_branch="release-candidate", ) unchecked_authors = get_unchecked_authors(self.github_access_token, org, repo) slack_usernames = self.translate_slack_usernames(unchecked_authors) pr = get_release_pr(self.github_access_token, org, repo) await self.say( channel_id=channel_id, text= "Release {version} for {project} was deployed! PR is up at {pr_url}." " These people have commits in this release: {authors}".format( version=version, authors=", ".join(slack_usernames), pr_url=pr.url, project=repo_info.name, )) await self.wait_for_checkboxes(repo_info, command_args.manager) command_args.loop.create_task(self.delay_message(repo_info))
async def finish_release(self, command_args): """ Merge the release candidate into the release branch, tag it, merge to master, and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info channel_id = repo_info.channel_id repo_url = repo_info.repo_url org, repo = get_org_and_repo(repo_url) pr = get_release_pr( github_access_token=self.github_access_token, org=org, repo=repo, ) if not pr: raise ReleaseException("No release currently in progress for {project}".format(project=repo_info.name)) version = pr.version finish_release( github_access_token=self.github_access_token, repo_url=repo_url, version=version, timezone=self.timezone ) await self.say( channel_id=channel_id, text="Merged evil scheme {version} for {project}! Now deploying to production...".format( version=version, project=repo_info.name, ), ) await wait_for_deploy( github_access_token=self.github_access_token, repo_url=repo_url, hash_url=repo_info.prod_hash_url, watch_branch="release", ) await self.say( channel_id=channel_id, text="My evil scheme {version} for {project} has been released to production. " "And by 'released', I mean completely...um...leased.".format( version=version, project=repo_info.name, ) )
async def release_command(self, command_args): """ Start a new release and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info repo_url = repo_info.repo_url org, repo = get_org_and_repo(repo_url) pr = get_release_pr( github_access_token=self.github_access_token, org=org, repo=repo, ) if pr: raise ReleaseException("A release is already in progress: {}".format(pr.url)) if repo_info.project_type == LIBRARY_TYPE: await self._library_release(command_args) elif repo_info.project_type == WEB_APPLICATION_TYPE: await self._web_application_release(command_args) else: raise Exception("Configuration error: unknown project type {}".format(repo_info.project_type))
def verify_new_commits(old_version): """Check if there are new commits to release""" if not any_new_commits(old_version): raise ReleaseException("No new commits to put in release")
async def verify_new_commits(old_version, *, base_branch, root): """Check if there are new commits to release""" if not await any_new_commits( old_version, base_branch=base_branch, root=root): raise ReleaseException("No new commits to put in release")