Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix release candidate procedure #8941

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions docs/src/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,12 @@ python release.py --help
- Create a **minor** release if the next release contains new or changed functionality.
- Create a **patch** release if the next release contains only bug fixes.

If you want to test the release (for example, deploy it to a test environment, or roll out a release to early adopters), it's possible to create a **release candidate** for a major, minor, or patch release.

If the release type is major, minor, or patch, update the [version overview](versioning.md#version-overview).
Before creating a release, it first needs to be tested as a **release candidate** for a major, minor, or patch release.
Aside from testing by automatic pipeline it can for example be deployed to a test environment, or rolled out to early adopters.
Before finalizing the release candidate, make sure to update the [version overview](versioning.md#version-overview).

```{important}
To determine whether a release is major, minor, or patch, compare the changes to the [previous most recent release](changelog.md), excluding release candidates.
To determine whether a release is major, minor, or patch, compare the changes to the [previous most recent release](changelog.md).
```

### Determine the version bump
Expand All @@ -496,32 +496,34 @@ Having decided on the release type, there are the following possibilities for th

- If the current release is a release candidate,
- and you want to create another release candidate, use: `rc`. If the current release is e.g. v3.6.1-rc.0, this will bump the version to v3.6.1-rc.1.
- and the next release will not be, use: `drop-rc`. If the current release is e.g. v3.6.1-rc.0, this will bump the version to v3.6.1.
- and changes have been made since the previous release candidate that impact the release type, use: `rc-major`, `rc-minor`, or `rc-patch`. If the current release is e.g. v3.6.1-rc.0, using `rc-minor` will bump the version to v3.7.0-rc.0.
- and the next release will not be, use: `release`. If the current release is e.g. v3.6.1-rc.0, this will bump the version to v3.6.1.
- and changes have been made since the previous release candidate that impact the release type, use: `major`, `minor`, or `patch`. If the current release is e.g. v3.6.1-rc.0, using `minor` will bump the version to v3.7.0-rc.0.
- If the current release is not a release candidate:
- and you want to create a release candidate, use: `rc-major`, `rc-minor`, or `rc-patch`. If the current release is e.g. v3.6.1, using `rc-minor` will bump the version to v3.7.0-rc.0.
- and you don't want to create a release candidate, use: `major`, `minor`, or `patch`. If the current release is e.g. v3.6.1, using `minor` will bump the version to v3.7.0.
- to create a release candidate, use: `major`, `minor`, or `patch`. If the current release is e.g. v3.6.1, using `minor` will bump the version to v3.7.0-rc.0.

### Check the preconditions

The release script will check a number of preconditions before actually creating the release. To check the preconditions
without releasing, invoke the release script with the version bump as determined:
The release script will check a number of preconditions before actually creating the release.
To check the preconditions without releasing, invoke the release script with the version bump as determined:

```console
python release.py --check-preconditions-only <bump> # Where bump is major, minor, patch, rc-major, rc-minor, rc-patch, rc, or drop-rc
python release.py --check-preconditions-only <bump> # Where bump is major, minor, patch, rc, or release
```

If everything is ok, there is no output, and you can proceed creating the release. Otherwise, the release script will list the preconditions that have not been met and need fixing before you can create the release.
If everything is ok, there is no output, and you can proceed creating the release.
Otherwise, the release script will list the preconditions that have not been met and need fixing before you can create the release.

### Create the release

To release *Quality-time*, issue the release command (in the release folder) using the type of release you picked:
To release *Quality-time*, issue the release command (in the release folder) from an already created release candidate:

```console
python release.py <bump> # Where bump is major, minor, patch, rc-major, rc-minor, rc-patch, rc, or drop-rc
python release.py release
```

If all preconditions are met, the release script will bump the version numbers, update the change history, commit the changes, push the commit, tag the commit, and push the tag to GitHub. The [GitHub Actions release workflow](https://github.com/ICTU/quality-time/actions/workflows/release.yml) will then build the Docker images and push them to [Docker Hub](https://hub.docker.com/search?type=image&q=ictu/quality-time). It will also create an {index}`Software Bill of Materials (SBOM) <Software Bill of Materials (SBOM)>` for the release, which can be found under the "Artifacts" header of the workflow run.
If all preconditions are met, the release script will bump the version numbers, update the change history, commit the changes, push the commit, tag the commit, and push the tag to GitHub.
The [GitHub Actions release workflow](https://github.com/ICTU/quality-time/actions/workflows/release.yml) will then build the Docker images and push them to [Docker Hub](https://hub.docker.com/search?type=image&q=ictu/quality-time).
It will also create an {index}`Software Bill of Materials (SBOM) <Software Bill of Materials (SBOM)>` for the release, which can be found under the "Artifacts" header of the workflow run.

The Docker images are `quality-time_database`, `quality-time_renderer`, `quality-time_api_server`, `quality-time_collector`, `quality-time_notifier`, `quality-time_proxy`, `quality-time_testldap`, and `quality-time_frontend`. The images are tagged with the version number. We don't use the `latest` tag.

Expand Down
3 changes: 3 additions & 0 deletions release/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ replace = "sonar.projectVersion={new_version}"
filename = "../docs/src/changelog.md"
search = "[Unreleased]"
replace = "v{new_version} - {$RELEASE_DATE}"
include_bumps = [
"pre_release_label",
] # this is the only bump that produces a non-rc release

[[tool.bumpversion.files]]
filename = "../.env"
Expand Down
39 changes: 18 additions & 21 deletions release/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,9 @@ def parse_arguments() -> tuple[str, str, bool]:
epilog=epilog,
formatter_class=RawDescriptionHelpFormatter,
)
allowed_bumps_in_rc_mode = [
"rc",
"rc-major",
"rc-minor",
"rc-patch",
"drop-rc",
] # rc = release candidate
allowed_bumps = ["rc-patch", "rc-minor", "rc-major", "patch", "minor", "major"]
bumps = allowed_bumps_in_rc_mode if "rc" in current_version else allowed_bumps
bumps = ["patch", "minor", "major"]
if "rc" in current_version:
bumps += ["rc", "release"]
parser.add_argument("bump", choices=bumps)
parser.add_argument(
"-c",
Expand All @@ -80,7 +74,8 @@ def check_preconditions(bump: str, current_version: str) -> None:
root = release_folder.parent
messages.extend(failed_preconditions_repo(root))
messages.extend(failed_preconditions_changelog(bump, root))
messages.extend(failed_preconditions_version_overview(current_version, root))
if bump == "release": # don't update the version overview for release candidates
messages.extend(failed_preconditions_version_overview(current_version, root))
if messages:
formatted_messages = "\n".join([f"- {message}" for message in messages])
sys.exit(f"Please fix these issues before releasing Quality-time:\n{formatted_messages}\n")
Expand Down Expand Up @@ -114,7 +109,7 @@ def failed_preconditions_changelog(bump: str, root: pathlib.Path) -> list[str]:
changelog_text = changelog_file.read()
if "[Unreleased]" not in changelog_text:
messages.append(f"The changelog ({changelog}) has no '[Unreleased]' header.")
if bump == "drop-rc" and "-rc." in changelog_text:
if bump == "release" and "-rc." in changelog_text:
messages.append(
f"The changelog ({changelog}) still contains release candidates; remove "
"the release candidates and move their changes under the '[Unreleased]' header."
Expand All @@ -129,8 +124,9 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa
version_overview_lines = version_overview_file.readlines()
missing = f"The version overview ({version_overview}) does not contain"
previous_line = ""
target_version = current_version.split("-rc.")[0]
for line in version_overview_lines:
if line.startswith(f"| v{current_version} "):
if line.startswith(f"| v{target_version} "):
if previous_line.startswith("| v"):
today = utc_today().isoformat()
release_date = previous_line.split(" | ")[1].strip()
Expand All @@ -139,7 +135,7 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa
return [] # All good: current version, next version, and release date found
return [f"{missing}) the new version."]
previous_line = line
return [f"{missing} the current version ({current_version})."]
return [f"{missing} the target version ({target_version})."]


def utc_today() -> datetime.date:
Expand All @@ -151,18 +147,19 @@ def main() -> None:
"""Create the release."""
os.environ["RELEASE_DATE"] = utc_today().isoformat() # Used by bump-my-version to update CHANGELOG.md
bump, current_version, check_preconditions_only = parse_arguments()
# See https://github.com/callowayproject/bump-my-version?tab=readme-ov-file#add-support-for-pre-release-versions
# for how bump-my-version deals with pre-release versions
check_preconditions(bump, current_version)
if check_preconditions_only:
return
# See https://github.com/callowayproject/bump-my-version?tab=readme-ov-file#add-support-for-pre-release-versions
# for how bump-my-version deals with pre-release versions
if bump.startswith("rc-"):
bump = bump.split("-", maxsplit=1)[1] # Create a patch, minor, or major release candidate
elif bump == "drop-rc":
bump = "pre_release_label" # Bump the pre-release label from "rc" to "final" (which is optional and omitted)
cmd = ["bump-my-version", "bump"]
if bump == "release":
cmd.append("pre_release_label") # Bump the pre-release label from "rc" to "final" (being optional and omitted)
elif bump == "rc":
bump = "pre_release_number" # Bump the release candidate number
subprocess.run(("bump-my-version", "bump", bump), check=True) # noqa: S603
cmd.append("pre_release_number") # Bump the release candidate number, when already on a -rc version
else:
cmd.append(bump)
subprocess.run(cmd, check=True) # noqa: S603
subprocess.run(("git", "push", "--follow-tags"), check=True) # noqa: S603


Expand Down