From 4e7053f3bd0663fef3c4c7194fb428623a4e30a1 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 27 Mar 2025 12:52:43 +0100 Subject: [PATCH 1/5] Add releasing documentation Closes: #119 Signed-off-by: Dmitry Tantsur --- docs/releasing.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 docs/releasing.md diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 00000000..f7136f01 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,188 @@ +# Ironic Standalone Operator releasing + +This document details the steps to create a release for Ironic Standalone +Operator (IrSO). + +**NOTE**: Always follow [release documentation from the main +branch][this-document]. Release documentation in release branches may be +outdated. + +[this-document]: https://github.com/metal3-io/ironic-standalone-operator/blob/main/docs/releasing.md + +## Before making a release + +Things you should check before making a release: + +- Check the + [Metal3 release process](https://github.com/metal3-io/metal3-docs/blob/main/processes/releasing.md) + for high-level process and possible follow-up actions. +- Use the `./hack/verify-release.sh` script as helper to identify possible + issues to be addressed before creating any release tags. You will need to + generate an access token on Github to verify the release information. +- Add any missing versions and their branch names to the [list of versions + in the API][supported-versions]. +- Extend the [functional tests suite][suite_test] with new tests if needed: + - update [the list of known Ironic API versions][known-api-versions] using + the [Ironic API versions listing][api-versions-list]. + - upgrade from the newest version to `latest` with and without HA + - upgrade to the newest version from the one before it (with and without HA) + Hint: you can usually copy existing tests, modifying only the `spec.version` + field and the API versions on the `TestAssumptions` structure. + +[supported-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/api/v1alpha1/ironic_types.go#L23-L38 +[suite_test]: https://github.com/metal3-io/ironic-standalone-operator/blob/main/test/suite_test.go +[known-api-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/test/suite_test.go#L59-L69 +[api-versions-list]: https://docs.openstack.org/ironic/latest/contributor/webapi-version-history.html + +## Permissions + +Creating a release requires repository `write` permissions for: + +- Tag pushing +- Branch creation +- GitHub Release publishing + +These permissions are implicit for the org admins and repository admins. Release +team member gets his/her permissions via `metal3-release-team` membership. This +GitHub team has the required permissions in each repository required to release +IrSO. Adding person to the team gives him/her the necessary rights in all +relevant repositories in the organization. Individual persons should not be +given permissions directly. + +## Process + +IrSO uses [semantic versioning](https://semver.org). + +- Regular releases: `v0.x.y` +- Beta releases: `v0.x.y-beta.z` +- Release candidate releases: `v0.x.y-rc.z` + +### Repository setup + +Clone the repository: `git clone git@github.com:metal3-io/ironic-standalone-operator` + +or if using existing repository, verify your intended remote is set to +`metal3-io`: `git remote -v`. For this document, we assume it is `origin`. + +- If creating a new minor branch, identify the commit you wish to create the + branch from, and create a branch `release-0.x`: + `git checkout -b release-0.x` and push it to remote: + `git push origin release-0.x` to create it +- If creating a new patch release, use existing branch `release-0.x`: + `git checkout origin/release-0.x` + +### Prepare branch + +The following actions must be taken in a commit on the freshly created branch +(**not** on the main branch) before tagging: + +- Change [the default Ironic version][default-version] to the most recent + version of the Ironic image. +- Change the branch of IrSO itself from `latest` to `release-0.x` in two + places: [IMG in Makefile][img-makefile] and + [Kustomize configuration][kustomize]. + +[default-version]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/pkg/ironic/version.go#L14-L15 +[img-makefile]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/Makefile#L74-L75 +[kustomize]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/config/manager/manager.yaml#L47 + +### Tags + +First we create a primary release tag, that triggers release note creation and +image building processes. + +- Create a signed, annotated tag with: `git tag -s -a v0.x.y -m v0.x.y` +- Push the tags to the GitHub repository: `git push origin v0.x.y` + +This triggers two things: + +- GitHub action workflow for automated release process creates a draft release + in GitHub repository with correct content, comparing the pushed tag to + previous tag. Running actions are visible on the + [Actions](https://github.com/metal3-io/ironic-standalone-operator/actions) + page, and draft release will be visible on top of the + [Releases](https://github.com/metal3-io/ironic-standalone-operator/releases) + page. +- GH action `build-images-action` starts building release image with the release + tag in Jenkins, and it gets pushed to Quay. Make sure the release tag is + visible in + [Quay tags page](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags). + If the release tag build is not visible, check if the action has failed and + retrigger as necessary. + +We also need to create one or more tags for the Go modules ecosystem: + +- For any subdirectory with `go.mod` in it, create another Git tag with + directory prefix, i.e. `git tag apis/v0.x.y`, and `git tag test/v0.x.y`. This + enables the tags to be used as a Gomodule version for any downstream users. + + **NOTE**: Do not create annotated tags (`-a`, or implicitly via `-m` or `-s`) + for Go modules. Release notes expects only the main tag to be annotated, + otherwise it might create incorrect release notes. + +### Release notes + +Next step is to clean up the release note manually. Release note has been +generated by the `release` action, do not click the `Generate release notes` +button. In case there is issue with release action, you may rerun it via +`Actions` tab, or you can `make release-notes` to get a markdown file with +the release content to be inserted. + +- If release is not a beta or release candidate, check for duplicates, reverts, + and incorrect classifications of PRs, and whatever release creation tagged to + be manually checked. + - For any superseded PRs (like same dependency uplifted multiple times, or + commit revertions) that provide no value to the release, move them to + Superseded section. This way the changes are acknowledged to be part of the + release, but not overwhelming the important changes contained by the + release. +- If the release you're making is not a new major release, new minor release, + or a new patch release from the latest release branch, uncheck the box for + latest release. +- If it is a release candidate (RC) or a beta release, tick pre-release box. +- Save the release note as a draft, and have others review it. + +### Release artifacts + +We need to verify all release artifacts are correctly built or generated by the +release workflow. For a release, we should have the following artifacts: + +We can use `./hack/verify-release.sh` to check for existence of release artifacts, +which should include the following: + +Git tags pushed: + +- Primary release tag: `v0.x.y` +- Go module tags: `apis/v0.x.y` and `test/v0.x.y` + +Container image built and tagged at Quay registry: + +- [ironic-standalone-operator:v0.x.y](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags) + +Files included in the release page: + +- Source code + +### Make the release + +After everything is checked out, hit the `Publish` button your GitHub draft +release! + +## Post-release actions for new release branches + +Some post-release actions are needed if new minor or major branch was created. + +### Branch protection rules + +Branch protection rules need to be applied to the new release branch. Copy the +settings after the previous release branch, with the exception of +`Required tests` selection. Required tests can only be selected after new +keywords are implemented in Jenkins JJB, and project-infra, and have been run at +least once in the PR targeting the branch in question. Branch protection rules +require user to have `admin` permissions in the repository. + +## Additional actions outside this repository + +Further additional actions are required in the Metal3 project after IrSO release. +For that, please continue following the instructions provided in +[Metal3 release process](https://github.com/metal3-io/metal3-docs/blob/main/processes/releasing.md) From 2a226609c13fd4acb4233603dca62fe9163fbca4 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 12 Jun 2025 15:05:51 +0200 Subject: [PATCH 2/5] Update to the new releasing workflow Signed-off-by: Dmitry Tantsur --- .github/workflows/release.yaml | 136 +++++++++++++ Makefile | 22 +++ docs/releasing.md | 175 ++++++++++------- hack/tools/go.mod | 14 ++ hack/tools/go.sum | 12 ++ hack/tools/release/notes.go | 341 +++++++++++++++++++++++++++++++++ releasenotes/.gitkeep | 0 7 files changed, 626 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/release.yaml create mode 100644 hack/tools/go.mod create mode 100644 hack/tools/go.sum create mode 100644 hack/tools/release/notes.go create mode 100644 releasenotes/.gitkeep diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..409e5cf6 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,136 @@ +# This code is borrowed from https://github.com/kubernetes-sigs/cluster-api/blob/main/.github/workflows/release.yaml +name: Create Release + +on: + push: + branches: + - main + paths: + - 'releasenotes/*.md' + +permissions: {} + +jobs: + push_release_tags: + permissions: + contents: write + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.release-version.outputs.release_version }} + if: github.repository == 'metal3-io/ironic-standalone-operator' + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + - name: Get release version + id: release-version + run: | + if [[ ${{ steps.changed-files.outputs.all_changed_files_count }} != 1 ]]; then + echo "1 release notes file should be changed to create a release tag, found ${{ steps.changed-files.outputs.all_changed_files_count }}" + exit 1 + fi + for changed_file in ${{ steps.changed-files.outputs.all_changed_files }}; do + export RELEASE_VERSION=$(echo "${changed_file}" | grep -oP '(?<=/)[^/]+(?=\.md)') + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_ENV} + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_OUTPUT} + if [[ "${RELEASE_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "Valid semver: ${RELEASE_VERSION}" + else + echo "Invalid semver: ${RELEASE_VERSION}" + exit 1 + fi + done + - name: Determine the release branch to use + run: | + if [[ ${RELEASE_VERSION} =~ beta ]] || [[ ${RELEASE_VERSION} =~ alpha ]]; then + export RELEASE_BRANCH=main + echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> ${GITHUB_ENV} + echo "This is a beta or alpha release, will use release branch ${RELEASE_BRANCH}" + else + export RELEASE_BRANCH=release-$(echo ${RELEASE_VERSION} | sed -E 's/^v([0-9]+)\.([0-9]+)\..*$/\1.\2/') + echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> ${GITHUB_ENV} + echo "This is not a beta or alpha release, will use release branch ${RELEASE_BRANCH}" + fi + - name: Create or checkout release branch + run: | + if git show-ref --verify --quiet "refs/remotes/origin/${RELEASE_BRANCH}"; then + echo "Branch ${RELEASE_BRANCH} already exists" + git checkout "${RELEASE_BRANCH}" + else + git checkout -b "${RELEASE_BRANCH}" + git push origin "${RELEASE_BRANCH}" + echo "Created branch ${RELEASE_BRANCH}" + fi + - name: Validate tag does not already exist + run: | + if [[ -n "$(git tag -l "${RELEASE_VERSION}")" ]]; then + echo "Tag ${RELEASE_VERSION} already exists, exiting" + exit 1 + fi + - name: Create Release Tag + run: | + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git tag -a ${RELEASE_VERSION} -m ${RELEASE_VERSION} + git tag api/${RELEASE_VERSION} + git tag test/${RELEASE_VERSION} + git push origin ${RELEASE_VERSION} + git push origin api/${RELEASE_VERSION} + git push origin test/${RELEASE_VERSION} + echo "Created tags ${RELEASE_VERSION}, api/${RELEASE_VERSION}, and test/${RELEASE_VERSION}" + release: + name: create draft release + runs-on: ubuntu-latest + needs: push_release_tags + permissions: + contents: write + steps: + - name: Set env + run: echo "RELEASE_TAG=${RELEASE_TAG}" >> ${GITHUB_ENV} + env: + RELEASE_TAG: ${{needs.push_release_tags.outputs.release_tag}} + - name: checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + ref: ${{ env.RELEASE_TAG }} + - name: Calculate go version + run: echo "go_version=$(make go-version)" >> ${GITHUB_ENV} + - name: Set up Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ env.go_version }} + - name: generate release artifacts + run: | + make release-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: get release notes + run: | + curl -L "https://raw.githubusercontent.com/${{ github.repository }}/main/releasenotes/${{ env.RELEASE_TAG }}.md" \ + -o "${{ env.RELEASE_TAG }}.md" + - name: Release + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 + with: + draft: true + body_path: ${{ env.RELEASE_TAG }}.md + tag_name: ${{ env.RELEASE_TAG }} + build_irso: + permissions: + contents: read + needs: push_release_tags + name: Build IrSO container image + if: github.repository == 'metal3-io/ironic-standalone-operator' + uses: metal3-io/project-infra/.github/workflows/container-image-build.yml@main + with: + image-name: 'ironic-standalone-operator' + pushImage: true + ref: ${{ needs.push_release_tags.outputs.release_tag }} + secrets: + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/Makefile b/Makefile index 1e4cdf0e..2d11951d 100644 --- a/Makefile +++ b/Makefile @@ -304,3 +304,25 @@ catalog-push: ## Push a catalog image. ##@ helpers: go-version: ## Print the go version we use to compile our binaries and images @echo $(GO_VERSION) + +## -------------------------------------- +## Release +## -------------------------------------- +RELEASE_TAG ?= $(shell git describe --abbrev=0 2>/dev/null) +ifneq (,$(findstring -,$(RELEASE_TAG))) + PRE_RELEASE=true +endif +# the previous release tag, e.g., v1.7.0, excluding pre-release tags +PREVIOUS_TAG ?= $(shell git tag -l | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+" | sort -V | grep -B1 $(RELEASE_TAG) | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$$" | head -n 1 2>/dev/null) +RELEASE_DIR := out +RELEASE_NOTES_DIR := releasenotes + +$(RELEASE_DIR): + mkdir -p $(RELEASE_DIR)/ + +$(RELEASE_NOTES_DIR): + mkdir -p $(RELEASE_NOTES_DIR)/ + +.PHONY: release-notes +release-notes: $(RELEASE_NOTES_DIR) $(RELEASE_NOTES) + cd hack/tools && $(GO) run release/notes.go --releaseTag=$(RELEASE_TAG) > $(realpath $(RELEASE_NOTES_DIR))/$(RELEASE_TAG).md diff --git a/docs/releasing.md b/docs/releasing.md index f7136f01..117193c6 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,13 +1,11 @@ -# Ironic Standalone Operator releasing +# ironic-standalone-operator releasing -This document details the steps to create a release for Ironic Standalone -Operator (IrSO). +This document details the steps to create a release for +`ironic-standalone-operator` aka IrSO. -**NOTE**: Always follow [release documentation from the main -branch][this-document]. Release documentation in release branches may be -outdated. - -[this-document]: https://github.com/metal3-io/ironic-standalone-operator/blob/main/docs/releasing.md +**NOTE**: Always follow +[release documentation from the main branch](https://github.com/metal3-io/ironic-standalone-operator/blob/main/docs/releasing.md). +Release documentation in release branches may be outdated. ## Before making a release @@ -15,10 +13,15 @@ Things you should check before making a release: - Check the [Metal3 release process](https://github.com/metal3-io/metal3-docs/blob/main/processes/releasing.md) - for high-level process and possible follow-up actions. + for high-level process and possible follow-up actions - Use the `./hack/verify-release.sh` script as helper to identify possible - issues to be addressed before creating any release tags. You will need to - generate an access token on Github to verify the release information. + issues to be addressed before creating any release tags. It verifies issues + like: + - Verify any other direct or indirect dependency is uplifted to close any public + vulnerabilities + +To make sure the correct Ironic versions are supported and tested: + - Add any missing versions and their branch names to the [list of versions in the API][supported-versions]. - Extend the [functional tests suite][suite_test] with new tests if needed: @@ -29,9 +32,9 @@ Things you should check before making a release: Hint: you can usually copy existing tests, modifying only the `spec.version` field and the API versions on the `TestAssumptions` structure. -[supported-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/api/v1alpha1/ironic_types.go#L23-L38 +[supported-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/b630805cdd782a51845fc16086e5f64fa77e29af/api/v1alpha1/ironic_types.go#L23-L34 [suite_test]: https://github.com/metal3-io/ironic-standalone-operator/blob/main/test/suite_test.go -[known-api-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/test/suite_test.go#L59-L69 +[known-api-versions]: https://github.com/metal3-io/ironic-standalone-operator/blob/b630805cdd782a51845fc16086e5f64fa77e29af/test/suite_test.go#L56-L67 [api-versions-list]: https://docs.openstack.org/ironic/latest/contributor/webapi-version-history.html ## Permissions @@ -61,86 +64,97 @@ IrSO uses [semantic versioning](https://semver.org). Clone the repository: `git clone git@github.com:metal3-io/ironic-standalone-operator` -or if using existing repository, verify your intended remote is set to -`metal3-io`: `git remote -v`. For this document, we assume it is `origin`. +or if using existing repository, make sure origin is set to the fork and +upstream is set to `metal3-io`. Verify if your remote is set properly or not +by using following command `git remote -v`. + +- Fetch the remote (`metal3-io`): `git fetch upstream` +This makes sure that all the tags are accessible. -- If creating a new minor branch, identify the commit you wish to create the - branch from, and create a branch `release-0.x`: - `git checkout -b release-0.x` and push it to remote: - `git push origin release-0.x` to create it -- If creating a new patch release, use existing branch `release-0.x`: - `git checkout origin/release-0.x` +### Preparing a branch -### Prepare branch +IrSO requires a branch to be created and updated before the automation runs. +If (and only if) you're creating a release `v0.x.0` (i.e. a minor release): -The following actions must be taken in a commit on the freshly created branch -(**not** on the main branch) before tagging: +- Switch to the main branch: `git checkout main` + +- Identify the commit you wish to create the branch from, and create a branch + `release-0.x`: `git checkout -b release-0.x` and push it to remote: + `git push upstream release-0.x` to create it. Replace `upstream` with + the actual remote name for the upstream source (not your private fork). + +On the newly created branch: - Change [the default Ironic version][default-version] to the most recent version of the Ironic image. + - Change the branch of IrSO itself from `latest` to `release-0.x` in two places: [IMG in Makefile][img-makefile] and [Kustomize configuration][kustomize]. -[default-version]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/pkg/ironic/version.go#L14-L15 -[img-makefile]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/Makefile#L74-L75 -[kustomize]: https://github.com/metal3-io/ironic-standalone-operator/blob/e576bce1aea0a1dc198c0f15f006c8e56d6271f4/config/manager/manager.yaml#L47 - -### Tags +- Commit your changes, push the new branch and create a pull request: + - The commit and PR title should be 🌱 Prepare branch release-0.x: + -`git commit -S -s -m ":seedling: Prepare branch release-0.x"` + -`git push -u origin release-0.x` -First we create a primary release tag, that triggers release note creation and -image building processes. +Wait for the pull request to be merged before proceeding. -- Create a signed, annotated tag with: `git tag -s -a v0.x.y -m v0.x.y` -- Push the tags to the GitHub repository: `git push origin v0.x.y` +[default-version]: https://github.com/metal3-io/ironic-standalone-operator/blob/b630805cdd782a51845fc16086e5f64fa77e29af/pkg/ironic/version.go#L14-L15 +[img-makefile]: https://github.com/metal3-io/ironic-standalone-operator/blob/b630805cdd782a51845fc16086e5f64fa77e29af/Makefile#L74-L75 +[kustomize]: https://github.com/metal3-io/ironic-standalone-operator/blob/b630805cdd782a51845fc16086e5f64fa77e29af/config/manager/manager.yaml#L47 -This triggers two things: +### Creating Release Notes -- GitHub action workflow for automated release process creates a draft release - in GitHub repository with correct content, comparing the pushed tag to - previous tag. Running actions are visible on the - [Actions](https://github.com/metal3-io/ironic-standalone-operator/actions) - page, and draft release will be visible on top of the - [Releases](https://github.com/metal3-io/ironic-standalone-operator/releases) - page. -- GH action `build-images-action` starts building release image with the release - tag in Jenkins, and it gets pushed to Quay. Make sure the release tag is - visible in - [Quay tags page](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags). - If the release tag build is not visible, check if the action has failed and - retrigger as necessary. +- Switch to the main branch: `git checkout main` -We also need to create one or more tags for the Go modules ecosystem: +- Create a new branch for the release notes**: + `git checkout -b release-notes-0.x.x origin/main` -- For any subdirectory with `go.mod` in it, create another Git tag with - directory prefix, i.e. `git tag apis/v0.x.y`, and `git tag test/v0.x.y`. This - enables the tags to be used as a Gomodule version for any downstream users. +- Generate the release notes: `RELEASE_TAG=v0.x.x make release-notes` + - Replace `v0.x.x` with the new release tag you're creating. + - This command generates the release notes here + `releasenotes/.md` . - **NOTE**: Do not create annotated tags (`-a`, or implicitly via `-m` or `-s`) - for Go modules. Release notes expects only the main tag to be annotated, - otherwise it might create incorrect release notes. - -### Release notes - -Next step is to clean up the release note manually. Release note has been -generated by the `release` action, do not click the `Generate release notes` -button. In case there is issue with release action, you may rerun it via -`Actions` tab, or you can `make release-notes` to get a markdown file with -the release content to be inserted. - -- If release is not a beta or release candidate, check for duplicates, reverts, - and incorrect classifications of PRs, and whatever release creation tagged to - be manually checked. +- Next step is to clean up the release note manually. + - If release is not a beta or release candidate, check for duplicates, + reverts, and incorrect classifications of PRs, and whatever release + creation tagged to be manually checked. - For any superseded PRs (like same dependency uplifted multiple times, or - commit revertions) that provide no value to the release, move them to + commit revertion) that provide no value to the release, move them to Superseded section. This way the changes are acknowledged to be part of the release, but not overwhelming the important changes contained by the release. -- If the release you're making is not a new major release, new minor release, - or a new patch release from the latest release branch, uncheck the box for - latest release. -- If it is a release candidate (RC) or a beta release, tick pre-release box. -- Save the release note as a draft, and have others review it. + +- Commit your changes, push the new branch and create a pull request: + - The commit and PR title should be 🚀 Release v0.x.y: + -`git commit -S -s -m ":rocket: Release v0.x.x"` + -`git push -u origin release-notes-0.x.x` + - Important! The commit should only contain the release notes file, nothing + else, otherwise automation will not work. + +- Ask maintainers and release team members to review your pull request. + +Once PR is merged following GitHub actions are triggered: + +- GitHub action `Create Release` runs following jobs: + - GitHub job `push_release_tags` will create and push the tags. This action + will also create release branch if its missing and release is `rc` or + minor. + - GitHub job `create draft release` creates draft release. Don't publish the + release until release tag is visible in. Running actions are visible on the + [Actions](https://github.com/metal3-io/ironic-standalone-operator/actions) + page, and draft release will be visible on top of the + [Releases](https://github.com/metal3-io/ironic-standalone-operator/releases). + If the release you're making is not a new major release, new minor release, + or a new patch release from the latest release branch, uncheck the box for + latest release. If it is a release candidate (RC) or a beta release, + tick pre-release box. + - GitHub job `build_irso` build release images with the + release tag, and push them to Quay. Make sure the release tags are visible in + Quay tags pages: + - [IrSO](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags) + If the new release tag is not available for any of the images, check if the + action has failed and retrigger as necessary. ### Release artifacts @@ -153,9 +167,9 @@ which should include the following: Git tags pushed: - Primary release tag: `v0.x.y` -- Go module tags: `apis/v0.x.y` and `test/v0.x.y` +- Go module tags: `api/v0.x.y` and `test/v0.x.y` -Container image built and tagged at Quay registry: +Container images built and tagged at Quay registry: - [ironic-standalone-operator:v0.x.y](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags) @@ -181,6 +195,19 @@ keywords are implemented in Jenkins JJB, and project-infra, and have been run at least once in the PR targeting the branch in question. Branch protection rules require user to have `admin` permissions in the repository. +### Documentation + +Update the [user +guide](https://github.com/metal3-io/metal3-docs/tree/main/docs/user-guide/src): + +- Update [supported + versions](https://github.com/metal3-io/metal3-docs/blob/main/docs/user-guide/src/irso/introduction.md#supported-versions) + with the Ironic versions that the new release supports. + +- Consider if Ironic versions in the + [examples](https://github.com/metal3-io/metal3-docs/blob/main/docs/user-guide/src/irso/install-basics.md) + need updating if they are too old or no longer supported. + ## Additional actions outside this repository Further additional actions are required in the Metal3 project after IrSO release. diff --git a/hack/tools/go.mod b/hack/tools/go.mod new file mode 100644 index 00000000..5094f1ef --- /dev/null +++ b/hack/tools/go.mod @@ -0,0 +1,14 @@ +module github.com/metal3-io/ironic-standalone-operator/hack/tools + +go 1.23.7 + +require ( + github.com/blang/semver v3.5.1+incompatible + github.com/google/go-github v17.0.0+incompatible + golang.org/x/oauth2 v0.30.0 +) + +require ( + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect +) diff --git a/hack/tools/go.sum b/hack/tools/go.sum new file mode 100644 index 00000000..26f18f2e --- /dev/null +++ b/hack/tools/go.sum @@ -0,0 +1,12 @@ +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/tools/release/notes.go b/hack/tools/release/notes.go new file mode 100644 index 00000000..95299b65 --- /dev/null +++ b/hack/tools/release/notes.go @@ -0,0 +1,341 @@ +//go:build tools +// +build tools + +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "os/exec" + "strings" + + "errors" + + "github.com/blang/semver" + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +/* +This tool prints all the titles of all PRs from previous release to HEAD. +This needs to be run *before* a tag is created. + +Use these as the base of your release notes. +*/ + +const ( + features = ":sparkles: New Features" + bugs = ":bug: Bug Fixes" + documentation = ":book: Documentation" + warning = ":warning: Breaking Changes" + other = ":seedling: Others" + unknown = ":question: Sort these by hand" + superseded = ":recycle: Superseded or Reverted" + repoOwner = "metal3-io" + repoName = "ironic-standalone-operator" + warningTemplate = ":rotating_light: This is a %s. Use it only for testing purposes.\nIf you find any bugs, file an [issue](https://github.com/metal3-io/ironic-standalone-operator/issues/new/).\n\n" +) + +var ( + outputOrder = []string{ + warning, + features, + bugs, + documentation, + other, + unknown, + superseded, + } + toTag = flag.String("releaseTag", "", "The tag or commit to end to.") +) + +func main() { + flag.Parse() + os.Exit(run()) +} + +func latestTag() (string, error) { + if toTag != nil && *toTag != "" { + return *toTag, nil + } + return "", errors.New("RELEASE_TAG is not set") +} + +// lastTag returns the tag to start collecting commits from based on the latestTag. +// For pre-releases and minor releases, it returns the latest minor release tag +// (e.g., for v1.9.0, v1.9.0-beta.0, or v1.9.0-rc.0, it returns v1.8.0). +// For patch releases, it returns the latest patch release tag (e.g., for v1.9.1 it returns v1.9.0). +func lastTag(latestTag string) (string, error) { + if isBeta(latestTag) || isRC(latestTag) || isMinor(latestTag) { + if index := strings.LastIndex(latestTag, "-"); index != -1 { + latestTag = latestTag[:index] + } + latestTag = strings.TrimPrefix(latestTag, "v") + + semVersion, err := semver.New(latestTag) + if err != nil { + return "", fmt.Errorf("parsing semver for %s: %w", latestTag, err) + } + semVersion.Minor-- + lastReleaseTag := fmt.Sprintf("v%s", semVersion.String()) + return lastReleaseTag, nil + } + + latestTag = strings.TrimPrefix(latestTag, "v") + + semVersion, err := semver.New(latestTag) + if err != nil { + return "", fmt.Errorf("parsing semver for %s: %w", latestTag, err) + } + semVersion.Patch-- + lastReleaseTag := fmt.Sprintf("v%s", semVersion.String()) + return lastReleaseTag, nil +} + +func isBeta(tag string) bool { + return strings.Contains(tag, "-beta.") +} + +func isRC(tag string) bool { + return strings.Contains(tag, "-rc.") +} + +func isMinor(tag string) bool { + return strings.HasSuffix(tag, ".0") +} + +func run() int { + latestTag, err := latestTag() + if err != nil { + log.Fatalf("Failed to get latestTag: %v", err) + } + lastTag, err := lastTag(latestTag) + if err != nil { + log.Fatalf("Failed to get lastTag: %v", err) + } + + commitHash, err := getCommitHashFromNewTag(latestTag) + if err != nil { + log.Fatalf("Failed to get commit hash from latestTag %s: %v", latestTag, err) + } + + cmd := exec.Command("git", "rev-list", lastTag+".."+commitHash, "--merges", "--pretty=format:%B") // #nosec G204:gosec + + merges := map[string][]string{ + features: {}, + bugs: {}, + documentation: {}, + warning: {}, + other: {}, + unknown: {}, + superseded: {}, + } + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Println("Error") + fmt.Println(string(out)) + return 1 + } + + commits := []*commit{} + outLines := strings.Split(string(out), "\n") + for _, line := range outLines { + line = strings.TrimSpace(line) + last := len(commits) - 1 + switch { + case strings.HasPrefix(line, "commit"): + commits = append(commits, &commit{}) + case strings.HasPrefix(line, "Merge"): + commits[last].merge = line + continue + case line == "": + default: + commits[last].body = line + } + } + + for _, c := range commits { + body := strings.TrimSpace(c.body) + var key, prNumber, fork string + switch { + case strings.HasPrefix(body, ":sparkles:"), strings.HasPrefix(body, "✨"): + key = features + body = strings.TrimPrefix(body, ":sparkles:") + body = strings.TrimPrefix(body, "✨") + case strings.HasPrefix(body, ":bug:"), strings.HasPrefix(body, "🐛"): + key = bugs + body = strings.TrimPrefix(body, ":bug:") + body = strings.TrimPrefix(body, "🐛") + case strings.HasPrefix(body, ":book:"), strings.HasPrefix(body, "📖"): + key = documentation + body = strings.TrimPrefix(body, ":book:") + body = strings.TrimPrefix(body, "📖") + case strings.HasPrefix(body, ":seedling:"), strings.HasPrefix(body, "🌱"): + key = other + body = strings.TrimPrefix(body, ":seedling:") + body = strings.TrimPrefix(body, "🌱") + case strings.HasPrefix(body, ":running:"), strings.HasPrefix(body, "🏃"): + // This has been deprecated in favor of :seedling: + key = other + body = strings.TrimPrefix(body, ":running:") + body = strings.TrimPrefix(body, "🏃") + case strings.HasPrefix(body, ":warning:"), strings.HasPrefix(body, "⚠️"): + key = warning + body = strings.TrimPrefix(body, ":warning:") + body = strings.TrimPrefix(body, "⚠️") + case strings.HasPrefix(body, ":rocket:"), strings.HasPrefix(body, "🚀"): + continue + default: + key = unknown + } + + body = strings.TrimSpace(body) + if body == "" { + continue + } + body = fmt.Sprintf("- %s", body) + + if strings.HasPrefix(c.merge, "Merge pull request") { + _, err = fmt.Sscanf(c.merge, "Merge pull request %s from %s", &prNumber, &fork) + if err != nil { + log.Fatalf("Error parsing merge commit message: %v", err) + } + } else if strings.HasPrefix(c.merge, "Merge commit from fork") { + _, err = fmt.Sscanf(c.merge, "Merge commit from fork") + if err != nil { + log.Fatalf("Error parsing merge commit message: %v", err) + } + } else { + log.Fatalf("Unexpected merge commit message format: %s", c.merge) + } + + merges[key] = append(merges[key], formatMerge(body, prNumber)) + } + + // Add empty superseded section, if not beta/rc, we don't cleanup those notes + if !isBeta(latestTag) && !isRC(latestTag) { + merges[superseded] = append(merges[superseded], "- ``") + } + + fmt.Println("") + if isBeta(latestTag) { + fmt.Printf(warningTemplate, "BETA RELEASE") + } + if isRC(latestTag) { + fmt.Printf(warningTemplate, "RELEASE CANDIDATE") + } + // TODO Turn this into a link (requires knowing the project name + organization) + fmt.Printf("# Changes since %v\n\n", lastTag) + + // print the changes by category + for _, key := range outputOrder { + mergeslice := merges[key] + if len(mergeslice) > 0 { + fmt.Printf("## %v\n\n", key) + for _, merge := range mergeslice { + fmt.Println(merge) + } + fmt.Println() + } + + // if we're doing beta/rc, print breaking changes and hide the rest of the changes + if key == warning { + if isBeta(latestTag) || isRC(latestTag) { + fmt.Printf("
\n") + fmt.Printf("More details about the release\n\n") + } + } + } + + // then close the details if we had it open + if isBeta(latestTag) || isRC(latestTag) { + fmt.Printf("
\n\n") + } + + fmt.Printf("The image for this release is: %v\n", latestTag) + fmt.Println("\n_Thanks to all our contributors!_ 😊") + + return 0 +} + +type commit struct { + merge string + body string +} + +func formatMerge(line, prNumber string) string { + if prNumber == "" { + return line + } + return fmt.Sprintf("%s (%s)", line, prNumber) +} + +// getCommitHashFromNewTag returns the latest commit hash for the specified tag. +// For minor and pre releases, it returns the main branch's latest commit. +// For patch releases, it returns the latest commit on the corresponding release branch. +func getCommitHashFromNewTag(newTag string) (string, error) { + token := os.Getenv("GITHUB_TOKEN") + if token == "" { + return "", errors.New("GITHUB_TOKEN is required") + } + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + branch := "main" + if !isBeta(newTag) { + branch = getReleaseBranchFromTag(newTag) + // Check if branch exist in upstream or not + _, _, err := client.Repositories.GetBranch(ctx, repoOwner, repoName, branch) + if err != nil { + // If branch does not exist, defaults to main + branch = "main" + } + } + + ref, _, err := client.Git.GetRef(ctx, repoOwner, repoName, "refs/heads/"+branch) + if err != nil { + log.Fatalf("Error fetching ref: %v", err) + } + commitHash := ref.GetObject().GetSHA() + return commitHash, nil +} + +func trimPrereleasePrefix(version string) string { + if idx := strings.Index(version, "-"); idx != -1 { + return version[:idx] + } + return version +} + +func getReleaseBranchFromTag(tag string) string { + tag = strings.TrimPrefix(tag, "v") + tag = trimPrereleasePrefix(tag) + if index := strings.LastIndex(tag, "."); index != -1 { + tag = tag[:index] + } + return fmt.Sprintf("release-%s", tag) +} diff --git a/releasenotes/.gitkeep b/releasenotes/.gitkeep new file mode 100644 index 00000000..e69de29b From 7c9e55b3ac85be77be5cc45267ccfd01410ea4bf Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 12 Jun 2025 15:13:30 +0200 Subject: [PATCH 3/5] Change the guidance on the branch preparation commit Signed-off-by: Dmitry Tantsur --- docs/releasing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index 117193c6..f7bdebed 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -93,8 +93,10 @@ On the newly created branch: [Kustomize configuration][kustomize]. - Commit your changes, push the new branch and create a pull request: - - The commit and PR title should be 🌱 Prepare branch release-0.x: - -`git commit -S -s -m ":seedling: Prepare branch release-0.x"` + - The commit and PR title should be + ✨ Switch to Ironic X.Y by default, prepare release-0.x: + -`git commit -S -s -m ":sparkles: Switch to Ironic X.Y by default, prepare release-0.x"` + where X.Y is the Ironic branch you used [above][default-version] -`git push -u origin release-0.x` Wait for the pull request to be merged before proceeding. From b7c90212d3a83f18f66fd7f286e4009555a77242 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 12 Jun 2025 15:24:12 +0200 Subject: [PATCH 4/5] Modernize the releasing documentation Signed-off-by: Dmitry Tantsur --- .github/workflows/release.yaml | 5 --- Makefile | 4 --- docs/releasing.md | 57 +++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 409e5cf6..e036db44 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -104,11 +104,6 @@ jobs: uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ env.go_version }} - - name: generate release artifacts - run: | - make release-notes - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: get release notes run: | curl -L "https://raw.githubusercontent.com/${{ github.repository }}/main/releasenotes/${{ env.RELEASE_TAG }}.md" \ diff --git a/Makefile b/Makefile index 2d11951d..dc5052d2 100644 --- a/Makefile +++ b/Makefile @@ -314,12 +314,8 @@ ifneq (,$(findstring -,$(RELEASE_TAG))) endif # the previous release tag, e.g., v1.7.0, excluding pre-release tags PREVIOUS_TAG ?= $(shell git tag -l | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+" | sort -V | grep -B1 $(RELEASE_TAG) | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$$" | head -n 1 2>/dev/null) -RELEASE_DIR := out RELEASE_NOTES_DIR := releasenotes -$(RELEASE_DIR): - mkdir -p $(RELEASE_DIR)/ - $(RELEASE_NOTES_DIR): mkdir -p $(RELEASE_NOTES_DIR)/ diff --git a/docs/releasing.md b/docs/releasing.md index f7bdebed..793c1bbc 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -39,18 +39,17 @@ To make sure the correct Ironic versions are supported and tested: ## Permissions -Creating a release requires repository `write` permissions for: +Creating the first release in a series requires repository `write` permissions +for branch creation. -- Tag pushing -- Branch creation -- GitHub Release publishing +These permissions are implicit for the org admins and repository admins. +Release team member gets his/her permissions via `metal3-release-team` +membership. This GitHub team has the required permissions in each repository +required to release IrSO. Adding person to the team gives him/her the necessary +rights in all relevant repositories in the organization. Individual persons +should not be given permissions directly. -These permissions are implicit for the org admins and repository admins. Release -team member gets his/her permissions via `metal3-release-team` membership. This -GitHub team has the required permissions in each repository required to release -IrSO. Adding person to the team gives him/her the necessary rights in all -relevant repositories in the organization. Individual persons should not be -given permissions directly. +Patch releases don't require extra permissions. ## Process @@ -83,10 +82,13 @@ If (and only if) you're creating a release `v0.x.0` (i.e. a minor release): `git push upstream release-0.x` to create it. Replace `upstream` with the actual remote name for the upstream source (not your private fork). -On the newly created branch: +- Setup the CI for the new branch in the prow configuration. [Prior + art](https://github.com/metal3-io/project-infra/pull/1034). + +Create a development branch (e.g. `prepare-0.x`) from the newly created branch: - Change [the default Ironic version][default-version] to the most recent - version of the Ironic image. + branch of the Ironic image. - Change the branch of IrSO itself from `latest` to `release-0.x` in two places: [IMG in Makefile][img-makefile] and @@ -97,7 +99,8 @@ On the newly created branch: ✨ Switch to Ironic X.Y by default, prepare release-0.x: -`git commit -S -s -m ":sparkles: Switch to Ironic X.Y by default, prepare release-0.x"` where X.Y is the Ironic branch you used [above][default-version] - -`git push -u origin release-0.x` + -`git push -u origin prepare-0.x` + - The pull request must target the new branch (`release-0.x`), not `main`! Wait for the pull request to be merged before proceeding. @@ -161,10 +164,8 @@ Once PR is merged following GitHub actions are triggered: ### Release artifacts We need to verify all release artifacts are correctly built or generated by the -release workflow. For a release, we should have the following artifacts: - -We can use `./hack/verify-release.sh` to check for existence of release artifacts, -which should include the following: +release workflow. You can use `./hack/verify-release.sh` to check for existence +of release artifacts, which should include the following: Git tags pushed: @@ -175,9 +176,7 @@ Container images built and tagged at Quay registry: - [ironic-standalone-operator:v0.x.y](https://quay.io/repository/metal3-io/ironic-standalone-operator?tab=tags) -Files included in the release page: - -- Source code +You can also check the draft release and its tags in the Github UI. ### Make the release @@ -191,11 +190,8 @@ Some post-release actions are needed if new minor or major branch was created. ### Branch protection rules Branch protection rules need to be applied to the new release branch. Copy the -settings after the previous release branch, with the exception of -`Required tests` selection. Required tests can only be selected after new -keywords are implemented in Jenkins JJB, and project-infra, and have been run at -least once in the PR targeting the branch in question. Branch protection rules -require user to have `admin` permissions in the repository. +settings after the previous release branch. Branch protection rules require +user to have `admin` permissions in the repository. ### Documentation @@ -210,6 +206,17 @@ guide](https://github.com/metal3-io/metal3-docs/tree/main/docs/user-guide/src): [examples](https://github.com/metal3-io/metal3-docs/blob/main/docs/user-guide/src/irso/install-basics.md) need updating if they are too old or no longer supported. +### Update milestones + +- Make sure the next two milestones exist. For example, after 0.4 is out, 0.5 + and 0.6 should exist in Github. +- Set the next milestone date based on the expected release date, which usually + happens shortly after the next Ironic release. +- Remove milestone date for passed milestones. + +Milestones must also be updated in the Prow configuration +([example](https://github.com/metal3-io/project-infra/pull/1035)). + ## Additional actions outside this repository Further additional actions are required in the Metal3 project after IrSO release. From febe248e99a112ccbd5e12c5061f22f23afe3c96 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Mon, 16 Jun 2025 16:51:19 +0200 Subject: [PATCH 5/5] Remove the not-yet-exisiting link Signed-off-by: Dmitry Tantsur --- docs/releasing.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index 793c1bbc..68dfe7ae 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -3,9 +3,8 @@ This document details the steps to create a release for `ironic-standalone-operator` aka IrSO. -**NOTE**: Always follow -[release documentation from the main branch](https://github.com/metal3-io/ironic-standalone-operator/blob/main/docs/releasing.md). -Release documentation in release branches may be outdated. +**NOTE**: Always follow release documentation from the main branch. Release +documentation in release branches may be outdated. ## Before making a release