diff --git a/docs/release_process.md b/docs/release_process.md index 82a297303cee..9fa18fc56ca1 100644 --- a/docs/release_process.md +++ b/docs/release_process.md @@ -1,9 +1,27 @@ # Releasing AWX (and awx-operator) -The release process for AWX is completely automated as of version 19.5.0. +The release process for AWX is mostly automated as of version 19.5.0. If you need to revert a release, please refer to the [Revert a Release](#revert-a-release) section. -## Get latest release version and list of new work + +## Select the next release version + +There are two methods you can use to get the next release version. The manual way and the automated way. + +### Automated Way + +#### Get a github token + +Log into your github account, under your user icon go to Settings => Developer Settings => Personal Access Tokens => Tokens (classic). +Select the Generate new token => Generate new token (classic) +Fill in the note, select no scopes select "Generate token". +Copy the token and create a file in your awx repo called `.github_creds`. Enter the token in this file. +Run `./tools/scripts/get_next_release.py` +This will use your token to go query for the PRs in the release and scan their bodies to select X/Y/Z and suggest new versions and spit out notifications. + +### Manual Way + +#### Get latest release version and list of new work 1. Open the main project page for [AWX](https://github.com/ansible/awx/releases) and [AWX Operator](https://github.com/ansible/awx-operator/releases). @@ -20,10 +38,10 @@ The page will now automatically update with a list of PRs that are in `AWX/devel ![PR Compare List](img/pr_compare_list.png) -## Select the next release version - Use this list of PRs to decide if this is a X-stream (major) release, Y-stream (minor) release, or a Z-stream (patch) release. Use [semver](https://semver.org/#summary) to help determine what kind of release is needed. +#### Select the next release version + Indicators of a Z-stream release: - No significant new features have been merged into devel since the last release. @@ -126,16 +144,19 @@ This workflow will take the generated images and promote them to quay.io. ![Verify released awx-operator image](img/verify-released-awx-operator-image.png) -## Notify the AWX mailing list -Send an email to the [AWX Mailing List](mailto:awx-project@googlegroups.com) with a message format of type "AWX Release" from the [mailing list triage standard replies](../.github/triage_replies.md#awx-release) +## Send notifications +Send notifications to the following groups: + * AWX Mailing List + * #social:ansible.com IRC (@newsbot for inclusion in bullhorn) + * #awx:ansible.com (no @newsbot in this room) + * #ansible-controller slack channel -## Send an IRC message over matrix to #social:ansible.com for bullhorn: +These messages are templated out for you in the output of `get_next_release.yml`. -@newsbot -We're happy to announce that [AWX version 21.1.0](https://github.com/ansible/awx/releases/tag/21.1.0) is now available! -We're happy to announce that [AWX Operator version 0.22.0](https://github.com/ansible/awx-operator/releases/tag/0.22.0) is now available! +Note: the slack message is the same as the IRC message. -## Send the same IRC message (less the @newsbot) to #awx:ansible.com +## Create operator hub PRs. +Operator hub PRs are generated via an Ansible Playbook. See someone on the AWX team for the location of the playbooks and instructions on how to run them. ## Revert a Release @@ -165,4 +186,4 @@ Here are the steps needed to revert an AWX and an AWX-Operator release. Dependin 7. Navigate to the [PyPi](https://pypi.org/project/awxkit/#history) and delete the bad AWX tag and release that got published. -8. [Restart the Release Process](#releasing-awx-and-awx-operator) \ No newline at end of file +8. [Restart the Release Process](#releasing-awx-and-awx-operator) diff --git a/tools/scripts/get_next_release.py b/tools/scripts/get_next_release.py new file mode 100755 index 000000000000..33e50a05661c --- /dev/null +++ b/tools/scripts/get_next_release.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python + +missing_modules = [] +try: + import requests +except: + missing_modules.append('requests') +import json +import os +import re +import sys +import time + +try: + import semantic_version +except: + missing_modules.append('semantic_version') + +if len(missing_modules) > 0: + print("This requires python libraries to work; try:") + for a_module in missing_modules: + print(" pip install {}".format(a_module)) + sys.exit(1) + + +def getCurrentVersions(): + print("Getting current versions") + for repo in product_repos: + response = session.get('https://api.github.com/repos/ansible/{}/releases'.format(repo)) + if 'X-RateLimit-Limit' in response.headers and int(response.headers['X-RateLimit-Limit']) <= 60: + print("Your key in .github_creds did not work right and you are using unauthenticated requests") + print("This script would likely overrun your available requests, exiting") + sys.exit(3) + versions['current'][repo] = response.json()[0]['tag_name'] + print(" {}: {}".format(repo, versions['current'][repo])) + + +def getNextReleases(): + # + # This loads the commits since the last release and gets their associated PRs and scans those for release_type: [xyz] + # Any X or Y changes also get captured for bullhorn release notes + # + for repo in product_repos: + response = session.get('https://api.github.com/repos/ansible/{}/compare/{}...devel'.format(repo, versions['current'][repo])) + commit_data = response.json() + pr_votes = {} + suggested_release_type = None + prs_missing_relese_type = 0 + versions['release_notes'][repo] = [] + for commit in commit_data['commits']: + response = session.get('https://api.github.com/repos/ansible/{}/commits/{}/pulls'.format(repo, commit['sha'])) + prs = response.json() + for a_pr in prs: + # If we've already seen this PR we don't need to check again + try: + if a_pr['html_url'] in pr_votes: + continue + except: + print("Unable to check on PR") + print(json.dumps(a_pr, indent=4)) + sys.exit(255) + append_title = False + pr_release = 'is non voting' + if a_pr and a_pr.get('body', None): + if 'Breaking Change' in a_pr['body']: + suggested_release_type = 'x' + pr_release = 'votes x' + append_title = True + elif 'New or Enhanced Feature' in a_pr['body']: + if suggested_release_type != 'x': + suggested_release_type = 'y' + pr_release = 'votes y' + append_title = True + elif 'Bug, Docs Fix or other nominal change' in a_pr['body']: + if suggested_release_type == None: + suggested_release_type = 'z' + pr_release = 'votes z' + # This was a format along the way + elif 'Bug or Docs Fix' in a_pr['body']: + if suggested_release_type == None: + suggested_release_type = 'z' + pr_release = 'votes z' + # Old PR format + elif ( + '- Bug Report' in a_pr['body'] + or '- Bug Fix' in a_pr['body'] + or '- Bugfix Pull Request' in a_pr['body'] + or '- Documentation' in a_pr['body'] + or '- Docs Pull Request' in a_pr['body'] + ): + if suggested_release_type == None: + suggested_release_type = 'z' + pr_release = 'votes z (from old PR body)' + elif '- Feature Idea' in a_pr['body'] or '- Feature Pull Request' in a_pr['body']: + if suggested_release_type != 'x': + suggested_release_type = 'y' + pr_release = 'votes y (from old PR body)' + append_title = True + else: + prs_missing_relese_type += 1 + else: + prs_missing_relese_type += 1 + + if append_title: + versions['release_notes'][repo].append("* {}".format(a_pr['title'])) + print("PR {} {}".format(a_pr['html_url'], pr_release)) + pr_votes[a_pr['html_url']] = pr_release + + print("https://github.com/ansible/{}/compare/{}...devel".format(repo, versions['current'][repo])) + print("{} devel is {} commit(s) ahead of release {}".format(repo, commit_data['total_commits'], versions['current'][repo])) + if prs_missing_relese_type == 0: + print("\nAll commits voted, the release type suggestion is {}".format(suggested_release_type)) + else: + total_prs = len(pr_votes) + voted_prs = total_prs - prs_missing_relese_type + print("From {} commits, {} of {} PRs voted".format(commit_data['total_commits'], voted_prs, total_prs)) + if suggested_release_type: + print("\nOf commits with release type, the suggestion is {}".format(suggested_release_type)) + else: + print("\nNone of the commits had the release type indicated") + print() + + current_version = semantic_version.Version(versions['current'][repo]) + if suggested_release_type.lower() == 'x': + versions['next'][repo] = current_version.next_major() + elif suggested_release_type.lower() == 'y': + versions['next'][repo] = current_version.next_minor() + else: + versions['next'][repo] = current_version.next_patch() + + +# +# Load the users session information +# +session = requests.Session() +try: + print("Loading credentials") + with open(".github_creds", "r") as f: + password = f.read().strip() + session.headers.update({'Authorization': 'bearer {}'.format(password), 'Accept': 'application/vnd.github.v3+json'}) +except Exception: + print("Failed to load credentials from ./.github_creds") + sys.exit(255) + +versions = { + 'current': {}, + 'next': {}, + 'release_notes': {}, +} + +product_repos = ['awx', 'awx-operator'] + +# +# Get latest release version from releases page +# +getCurrentVersions() + +# +# Scan PRs for release types +# +getNextReleases() + +# +# Confirm the release number with the human +# +print( + ''' + +Next recommended releases: + AWX: {0} + Operator: {1} + +'''.format( + versions['next']['awx'], + versions['next']['awx-operator'], + ) +) + +for product in product_repos: + version_override = input("Enter the next {} release number ({}): ".format(product, versions['next'][product])) + if version_override != '': + versions['next'][product] = version_override + +# +# Generate IRC and Mailing list messages +# +print("Enter any known issues (one per line, empty line to end)") +known_issues = [] +keep_getting_issues = True +while keep_getting_issues: + issue = input() + if issue == '': + keep_getting_issues = False + else: + known_issues.append(issue) + +display_known_issues = '' +if len(known_issues) > 0: + display_known_issues = "\n".join(['Known Issues:'] + ['* {}'.format(item) for item in known_issues]) + +print( + ''' +Bullhorn/irc list message: + +@newsbot We're happy to announce that the next release of AWX, version {0} is now available! +Some notable features include: +{2} + +In addition AWX Operator version {1} has also been released! +Some notable features include: +{3} + +Please see the releases pages for more details: +AWX: [https://github.com/ansible/awx/releases/tag/{0}](https://github.com/ansible/awx/releases/tag/{0}) +Operator: [https://github.com/ansible/awx-operator/releases/tag/{1}](https://github.com/ansible/awx-operator/releases/tag/{1}) + +{4} +'''.format( + versions['next']['awx'], + versions['next']['awx-operator'], + '\n'.join(versions['release_notes']['awx']), + '\n'.join(versions['release_notes']['awx-operator']), + display_known_issues, + ) +) + +print( + ''' +Mailing list message: + +Subject: Announcing AWX {0} and AWX-Operator {1} +Body: +Hi all, + +We're happy to announce that the next release of AWX, version {0} is now available! +Some notable features include: +{2} + +In addition AWX Operator version {1} has also been released! +Some notable features include: +{3} + +Please see the releases pages for more details: +AWX: https://github.com/ansible/awx/releases/tag/{0} +Operator: https://github.com/ansible/awx-operator/releases/tag/{1} + +{4} + +-The AWX team. +'''.format( + versions['next']['awx'], + versions['next']['awx-operator'], + '\n'.join(versions['release_notes']['awx']), + '\n'.join(versions['release_notes']['awx-operator']), + display_known_issues, + ) +)