From a443217b91e037519e3490f7ba272d8e010ceba7 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Thu, 12 Sep 2024 18:56:39 +0200 Subject: [PATCH] WIP Release infra stuff Introduce a couple of shell scripts to automate the release process. Some docs are included under `releases/README.md`. Generally, releases at the ASF follow the following workflow: 1. Draft a release 2. Start a VOTE on the dev mailing list 3. If the VOTE fails, the release has failed - "go to step 1" 4. If the VOTE passes, publish the release The above process is, without release branches, reflected in the scripts: 1. `releases/bin/draft-release.sh --major --minor --commit ` 2. if the vote passes: `releases/bin/publish-release.sh --major --minor ` 3. if the vote fails, just run `draft-release.sh` again The change includes scripts to handle version branches, however, using those is not required for the two release scripts above. It's important to know that the scripts handle changes to the `version.txt` file and that a specific syntax for Git tags is expected, which is required to automatically use/generate RC and final versions, including the artifact publishing to Nexus. --- NEXT_RELEASE_NOTES.md | 22 + .../publishing/PublishingHelperPlugin.kt | 7 - .../src/main/kotlin/publishing/rootProject.kt | 142 +---- build.gradle.kts | 3 + releases/README.md | 123 +++++ releases/bin/_lib.sh | 277 ++++++++++ releases/bin/_releases_lib.sh | 60 +++ releases/bin/create-release-branch.sh | 158 ++++++ releases/bin/draft-release.sh | 504 ++++++++++++++++++ releases/bin/generate-release-notes.sh | 224 ++++++++ releases/bin/list-release-branches.sh | 39 ++ releases/bin/publish-release.sh | 427 +++++++++++++++ releases/test/.gitignore | 21 + releases/test/README.md | 46 ++ releases/test/setup-tests.sh | 53 ++ releases/test/test_draft-release.bats | 128 +++++ releases/test/test_lib.bats | 350 ++++++++++++ site/bin/checkout-releases.sh | 2 +- site/bin/remove-releases.sh | 37 +- site/content/in-dev/release_index.md | 5 +- 20 files changed, 2480 insertions(+), 148 deletions(-) create mode 100644 NEXT_RELEASE_NOTES.md create mode 100644 releases/README.md create mode 100644 releases/bin/_lib.sh create mode 100644 releases/bin/_releases_lib.sh create mode 100755 releases/bin/create-release-branch.sh create mode 100755 releases/bin/draft-release.sh create mode 100755 releases/bin/generate-release-notes.sh create mode 100755 releases/bin/list-release-branches.sh create mode 100755 releases/bin/publish-release.sh create mode 100644 releases/test/.gitignore create mode 100644 releases/test/README.md create mode 100755 releases/test/setup-tests.sh create mode 100644 releases/test/test_draft-release.bats create mode 100644 releases/test/test_lib.bats diff --git a/NEXT_RELEASE_NOTES.md b/NEXT_RELEASE_NOTES.md new file mode 100644 index 0000000000..75d4209ae1 --- /dev/null +++ b/NEXT_RELEASE_NOTES.md @@ -0,0 +1,22 @@ + diff --git a/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt b/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt index d4d412a30f..4f07852dc2 100644 --- a/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt +++ b/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt @@ -44,15 +44,8 @@ import org.gradle.plugins.signing.SigningPlugin * The task `sourceTarball` (available on the root project) generates a source tarball using `git * archive`. * - * The task `releaseEmailTemplate` generates the release-vote email subject + body. Outputs on the - * console and in the `build/distributions/` directory. - * * Signing tip: If you want to use `gpg-agent`, set the `useGpgAgent` Gradle project property * - * The following command publishes the project artifacts to your local maven repository, generates - * the source tarball - and uses `gpg-agent` to sign all artifacts and the tarball. Note that this - * requires a Git tag! - * * ``` * ./gradlew publishToMavenLocal sourceTarball -Prelease -PuseGpgAgent * ``` diff --git a/build-logic/src/main/kotlin/publishing/rootProject.kt b/build-logic/src/main/kotlin/publishing/rootProject.kt index 663d3a26d8..e121ba62df 100644 --- a/build-logic/src/main/kotlin/publishing/rootProject.kt +++ b/build-logic/src/main/kotlin/publishing/rootProject.kt @@ -19,14 +19,11 @@ package publishing -import io.github.gradlenexus.publishplugin.NexusPublishExtension import io.github.gradlenexus.publishplugin.NexusPublishPlugin -import io.github.gradlenexus.publishplugin.internal.StagingRepositoryDescriptorRegistryBuildService import org.gradle.api.Project -import org.gradle.api.services.BuildServiceRegistration +import org.gradle.api.tasks.Delete import org.gradle.api.tasks.Exec import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register import org.gradle.plugins.signing.Sign @@ -41,11 +38,22 @@ internal fun configureOnRootProject(project: Project) = val isRelease = project.hasProperty("release") val isSigning = isRelease || project.hasProperty("signArtifacts") + val cleanDistributionsDir = tasks.register("cleanDistributionsDir") + cleanDistributionsDir.configure { + outputs.cacheIf { false } + + val e = project.extensions.getByType(PublishingHelperExtension::class.java) + delete(e.distributionDir) + } + val sourceTarball = tasks.register("sourceTarball") sourceTarball.configure { group = "build" description = "Generate a source tarball for a release to be uploaded to dist.apache.org/repos/dist" + outputs.cacheIf { false } + + dependsOn(cleanDistributionsDir) val e = project.extensions.getByType(PublishingHelperExtension::class.java) doFirst { mkdir(e.distributionDir) } @@ -85,130 +93,4 @@ internal fun configureOnRootProject(project: Project) = } sourceTarball.configure { finalizedBy(signSourceTarball) } } - - val releaseEmailTemplate = tasks.register("releaseEmailTemplate") - releaseEmailTemplate.configure { - group = "publishing" - description = - "Generate release-vote email subject + body, including the staging repository URL, if run during the Maven release." - - mustRunAfter("initializeApacheStagingRepository") - - doFirst { - val e = project.extensions.getByType(PublishingHelperExtension::class.java) - val asfName = e.asfProjectId.get() - - val gitInfo = MemoizedGitInfo.gitInfo(rootProject) - val gitCommitId = gitInfo["Apache-Polaris-Build-Git-Head"] - - val repos = project.extensions.getByType(NexusPublishExtension::class.java).repositories - val repo = repos.iterator().next() - - val stagingRepositoryUrlRegistryRegistration = - gradle.sharedServices.registrations.named< - BuildServiceRegistration - >( - "stagingRepositoryUrlRegistry" - ) - val staginRepoUrl = - if (stagingRepositoryUrlRegistryRegistration.isPresent) { - val stagingRepositoryUrlRegistryBuildServiceRegistration = - stagingRepositoryUrlRegistryRegistration.get() - val stagingRepositoryUrlRegistryService = - stagingRepositoryUrlRegistryBuildServiceRegistration.getService() - if (stagingRepositoryUrlRegistryService.isPresent) { - val registry = stagingRepositoryUrlRegistryService.get().registry - try { - val stagingRepoDesc = registry.get(repo.name) - val stagingRepoId = stagingRepoDesc.stagingRepositoryId - "https://repository.apache.org/content/repositories/$stagingRepoId/" - } catch (e: IllegalStateException) { - "NO STAGING REPOSITORY ($e)" - } - } else { - "NO STAGING REPOSITORY (no registry service) !!" - } - } else { - "NO STAGING REPOSITORY (no build service) !!" - } - - val asfProjectName = - e.overrideName.orElse(project.provider { "Apache ${fetchAsfProjectName(asfName)}" }).get() - - val versionNoRc = version.toString().replace("-rc-?[0-9]+".toRegex(), "") - - val subjectFile = e.distributionFile("vote-email-subject.txt").relativeTo(projectDir) - val bodyFile = e.distributionFile("vote-email-body.txt").relativeTo(projectDir) - - val emailSubject = "[VOTE] Release $asfProjectName $version" - subjectFile.writeText(emailSubject) - - val emailBody = - """ - Hi everyone, - - I propose that we release the following RC as the official - $asfProjectName $versionNoRc release. - - * This corresponds to the tag: apache-$asfName-$version - * https://github.com/apache/$asfName/commits/apache-$asfName-$version - * https://github.com/apache/$asfName/tree/$gitCommitId - - The release tarball, signature, and checksums are here: - * https://dist.apache.org/repos/dist/dev/incubator/$asfName/apache-$asfName-$version - - You can find the KEYS file here: - * https://downloads.apache.org/incubator/$asfName/KEYS - - Convenience binary artifacts are staged on Nexus. The Maven repository URL is: - * $staginRepoUrl - - Please download, verify, and test. - - Please vote in the next 72 hours. - - [ ] +1 Release this as Apache $asfName $version - [ ] +0 - [ ] -1 Do not release this because... - - Only PPMC members and mentors have binding votes, but other community members are - encouraged to cast non-binding votes. This vote will pass if there are - 3 binding +1 votes and more binding +1 votes than -1 votes. - - NB: if this vote pass, a new vote has to be started on the Incubator general mailing - list. - - Thanks - Regards - """ - - logger.lifecycle( - """ - - - The email for your release vote mail: - ------------------------------------- - - Suggested subject: (also in file $subjectFile) - - $emailSubject - - Suggested body: (also in file $bodyFile) - - $emailBody - - """ - .trimIndent() - ) - bodyFile.writeText(emailBody.trimIndent()) - } - } - - if (isRelease) { - sourceTarball.configure { finalizedBy(releaseEmailTemplate) } - } - - afterEvaluate { - tasks.named("closeApacheStagingRepository") { mustRunAfter(releaseEmailTemplate) } - } } diff --git a/build.gradle.kts b/build.gradle.kts index 1604778cf9..a551be26d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -122,6 +122,9 @@ tasks.named("rat").configure { excludes.add("client/python/.openapi-generator/**") excludes.add("regtests/output/**") + excludes.add("releases/test/bats/**") + excludes.add("releases/test/test_helper/**") + excludes.add("**/*.ipynb") excludes.add("**/*.iml") excludes.add("**/*.iws") diff --git a/releases/README.md b/releases/README.md new file mode 100644 index 0000000000..fd9fbe1263 --- /dev/null +++ b/releases/README.md @@ -0,0 +1,123 @@ + + +# TODOs + +* Allow draft-release.sh to publish to Apache +* Close staging repo when RC is abandoned +* Adopt website (top bar menu, maintained releases, left site menu) + +# Apache Polaris Release Infrastructure + +This directory holds all the infrastructure parts that are required to draft a Polaris release (RC) and to eventually +release it. + +Releases can be created from any Git commit, version branches are supported. + +Generally, releases at the ASF follow the following workflow: + +1. Draft a release +2. Start a VOTE on the dev mailing list +3. If the VOTE fails, the release has failed - "go to step 1" +4. If the VOTE passes, publish the release + +The Polaris project decided to run all release related via GitHub actions - a release is never drafted nor actually +released from a developer machine. + +## Technical process + +In the `release/bin/` directory are the scripts that are required to draft a release and to publish it as "GA". All +scripts can be called with the `--help` argument to get some usage information. There is also a `--dry-run` option +to inspect what _would_ happen. + +The technical process for a release follows the workflow above: + +1. `releases/bin/draft-release.sh --major --minor --commit ` + creates a release-candidate. The `--commit` argument is optional and defaults to the HEAD of the local Git + worktree. The patch version number and RC-number are generated automatically. This means, that RC-numbers are + automatically incremented as long as there is no "final" release tag. In that case, the patch version number + is incremented and the RC-number is set to 1. + + The Git tag name that will be used follows the pattern `polaris-..-RC` + + The content of the `version.txt` file is set to the full release version, for example `1.2.3`. + + Gradle will run with the arguments `-Prelease publishToApache closeApacheStagingRepository sourceTarball`. + Both the release artifacts (jars) and the source tarball are signed. The signing and Nexus credentials must be + provided externally using the `ORG_GRADLE_PROJECT_*` environment variables. In dry-run mode, this step runs + Gradle with the arguments `-PjarWithGitInfo -PsignArtifacts -PuseGpgAgent publishToMavenLocal sourceTarball publishToMavenLocal`. + + The staging repository ID, which is needed to release the staging repository, and URL are extracted from the + Gradle output and memoized in the files `releases/current-release-staging-repository-id` and + `releases/current-release-staging-repository-url`. The source Git commit ID is memoized in the file + `releases/current-release-commit-id`. + + The source tarball will be uploaded to the Apache infrastructure. + + Release notes will be generated and included in the file `site/content/in-dev/unreleasd/release-notes.md`. This + makes the release notes available later on the project website within the versioned docs. + + The suggested release VOTE email subject and body with the correct links and information are provided. + + The last step is to push the Git tag with a Git commit containing the above file changes. +2. Once the release VOTE passed: `releases/bin/publish-release.sh --major --minor `. + + The command will find the latest RC tag for the latest patch release of the given major/minor version. + + The final Git tag name that will be used follows the pattern `polaris-..` and created from + the latest RC-tag for that version. + + The Git tag is then pushed. + + Documentation pages for the release will then be copied from the `site/content/in-dev/unreleased` into the + `site/content/releases/..` folder within the `versioned-docs` branch. + The changes for the updated `versioned-docs` branch are then pushed to Git. + + The suggested ANNOUNCEMENT email subject and body are provided. + + Creating a release in GitHub is the last step. + +## Technical requirements + +To use the scripts in the `releases/bin/` folder, it is required to have a _full_ clone with all tags and branches. +Shallow clones, which is the default when checking out a Git repository in GitHub actions, will _not_ work properly! + +On top, all scripts need privileges to be able to push tags and branches to the GitHub repository - this is an +essential requirement for the scripts to do their job. + +The `draft-release.sh` and `publish-release.sh` scripts also require the following environment variables providing +the necessary secrets: + +* `ORG_GRADLE_PROJECT_signingKey` +* `ORG_GRADLE_PROJECT_signingPassword` +* `ORG_GRADLE_PROJECT_sonatypeUsername` +* `ORG_GRADLE_PROJECT_sonatypePassword` + +GitHub actions running the scripts must provide those secrets and privileges. + +## Version branches + +The Polaris project may use maintenance version branches following the pattern `release/.x` and +`release/.`. The scripts mentioned above are already support version branches and have validations for +this use case. Using version branches is not a requirement for the release scripts. + +The two scripts `releases/bin/create-release-branch.sh` plus the informative `releases/bin/list-release-branches.sh` +are there to help with version branches. The former must be invoked on the main branch and creates a new major-version +branch using the `release/.x` pattern. The latter must be invoked on a major-version branch and creates a new +minor version branch using the `release/.` pattern. diff --git a/releases/bin/_lib.sh b/releases/bin/_lib.sh new file mode 100644 index 0000000000..e2752fc5a9 --- /dev/null +++ b/releases/bin/_lib.sh @@ -0,0 +1,277 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# +# Constants and generic functions for all release related scripts +# + +if [[ -z ${bin_dir} ]]; then + echo "bin_dir variable undefined, fix the issue in the scripts, aborting" > /dev/stderr + exit 1 +fi + +root_dir="$(realpath "${bin_dir}/../..")" +# shellcheck disable=SC2034 +worktree_dir="${root_dir}/build/releases-git-worktree" +if [[ ! -f ${root_dir}/version.txt ]]; then + echo "Looks like ${root_dir}/version.txt does not exist, aborting" > /dev/stderr + exit 1 +fi + +# Constants +# shellcheck disable=SC2034 +main_branch="releases-infra" +# shellcheck disable=SC2034 +release_branch_prefix="release/" +# shellcheck disable=SC2034 +release_branch_regex="^release\\/([0-9]+)[.](x|[0-9]+)([.](x|[0-9]+))?$" +# shellcheck disable=SC2034 +versioned_docs_branch="versioned-docs" +tag_prefix="apache-polaris-" +# shellcheck disable=SC2034 +rc_tag_regex="${tag_prefix}[0-9]+[.][0-9]+[.]([0-9]+)(-.+)*-rc([0-9]+)" +# shellcheck disable=SC2034 +release_tag_regex="${tag_prefix}([0-9]+[.][0-9]+[.][0-9]+-.+)*" +# When going becoming a TLP, replace 'incubator/polaris' with 'polaris' ! +project_release_dir_part="incubator/polaris" +# shellcheck disable=SC2034 +svn_dist_dev_repo="https://dist.apache.org/repos/dist/dev/${project_release_dir_part}/" +# shellcheck disable=SC2034 +svn_dist_release_repo="https://dist.apache.org/repos/dist/release/${project_release_dir_part}/" + +# common subdirectory for local SVN directories, always set from tests +[[ -z ${svn_dir_prefix} ]] && svn_dir_prefix="build" +# shellcheck disable=SC2034 +svn_dir_dev="${svn_dir_prefix}/svn-source-dev" +# shellcheck disable=SC2034 +svn_dir_release="${svn_dir_prefix}/svn-source-release" + + + +function get_podling_version_suffix { + local podling + podling="$(curl https://whimsy.apache.org/public/public_ldap_projects.json 2>/dev/null | jq --raw-output '.projects["polaris"]."podling"')" + if [[ ${podling} == "current" ]] ; then + echo "-incubating" + fi +} + +function start_group { + local heading + heading="${*}" + if [[ ${CI} ]] ; then + # For GitHub workflows + echo "::group::${heading}" + else + # For local runs + echo "" + echo "${heading}" + echo "-------------------------------------------------------------" + echo "" + fi +} + +function end_group { + if [[ ${CI} ]] ; then + # For GitHub workflows + echo "::endgroup::" + else + # For local runs + echo "" + echo "-------------------------------------------------------------" + echo "" + fi +} + +function exec_process { + local dry_run + dry_run=$1 + shift + + if [[ ${dry_run} -ne 1 ]]; then + echo "Executing '${*}'" + "$@" + return + else + echo "Dry-run, WOULD execute '${*}'" + fi +} + +# shellcheck disable=SC2034 +# shellcheck disable=SC2154 +version_txt="$(cat "${root_dir}"/version.txt)" +# shellcheck disable=SC2034 +current_branch="$(git branch --show-current)" +# The name of the Git remote that points to the Apache Polaris repo +upstream_name="$(git remote -v | grep 'https://github.com/apache/polaris.git' | grep -w '(push)' | cut -f1)" + +function list_release_branches { + local prefix + prefix="$1" + # shellcheck disable=SC2154 + git ls-remote --branches "${upstream_name}" "${release_branch_prefix}${prefix}*" | sed --regexp-extended 's/[0-9a-f]+\Wrefs\/heads\/(.*)/\1/' + return +} + +function list_release_tags { + local prefix + prefix="$1" + # shellcheck disable=SC2154 + git ls-remote --refs --tags "${upstream_name}" "${tag_prefix}${prefix}*" | sed --regexp-extended 's/[0-9a-f]+\Wrefs\/tags\/(.*)/\1/' + return +} + +function major_version_from_branch_name { + local r + # shellcheck disable=SC2154 + r="$(echo "$1" | sed --regexp-extended "s/${release_branch_regex}/\1/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function minor_version_from_branch_name { + local r + r="$(echo "$1" | sed --regexp-extended "s/${release_branch_regex}/\2/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function full_version_with_label_from_release_tag { + local r + r="$(echo "$1" | sed --regexp-extended "s/${release_tag_regex}/\1/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function patch_version_from_rc_tag { + local r + # shellcheck disable=SC2154 + r="$(echo "$1" | sed --regexp-extended "s/${rc_tag_regex}/\1/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function version_label_from_rc_tag { + local r + r="$(echo "$1" | sed --regexp-extended "s/${rc_tag_regex}/\2/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function rc_iteration_from_tag { + local r + r="$(echo "$1" | sed --regexp-extended "s/${rc_tag_regex}/\3/")" + [[ "$r" != "$1" ]] && echo "$r" +} + +function _get_max_patch_version { + max_patch=-1 + while read -r release_tag_name ; do + _patch="$(patch_version_from_rc_tag "${release_tag_name}")" + [[ $_patch -gt $max_patch ]] && max_patch=$_patch + done + echo "${max_patch}" +} + +function get_max_patch_version { + local major + local minor + major="$1" + minor="$2" + _get_max_patch_version < <(list_release_tags "${major}.${minor}.") +} + +function _get_max_rc_iteration { + max_rc=-1 + while read -r release_tag_name ; do + _rc="$(rc_iteration_from_tag "${release_tag_name}")" + if [[ -z ${_rc} ]]; then + max_rc="-2" + break + fi + [[ $_rc -gt $max_rc ]] && max_rc=$_rc + done + echo "${max_rc}" +} + +function get_max_rc_iteration { + local full_version + full_version="$1" + _get_max_rc_iteration < <(list_release_tags "${full_version}") +} + +function _tag_for_full_version { + while read -r release_tag_name ; do + _rc="$(rc_iteration_from_tag "${release_tag_name}")" + if [[ -z ${_rc} ]]; then + echo "${release_tag_name}" + break + fi + done +} + +function tag_for_full_version { + local full_version + full_version="$1" + _tag_for_full_version < <(list_release_tags "${full_version}") +} + +function _get_latest_rc_tag_name { + max_rc=-1 + tag="" + while read -r release_tag_name ; do + _rc="$(rc_iteration_from_tag "${release_tag_name}")" + if [[ -z ${_rc} ]]; then + max_rc="-2" + break + fi + if [[ $_rc -gt $max_rc ]]; then + max_rc=$_rc + tag="${release_tag_name}" + fi + done + echo "${tag}" +} + +function get_latest_rc_tag_name { + local full_version + full_version="$1" + _get_latest_rc_tag_name < <(list_release_tags "${full_version}") +} + +function _get_max_major_version { + max_major=-1 + while read -r release_branch_name ; do + _major="$(major_version_from_branch_name "${release_branch_name}")" + [[ $_major -gt $max_major ]] && max_major=$_major + done + echo "${max_major}" +} + +function get_max_major_version { + _get_max_major_version < <(list_release_branches "") +} + +function _get_max_minor_version { + max_minor=-1 + while read -r release_branch_name ;do + _minor="$(minor_version_from_branch_name "${release_branch_name}")" + [[ $_minor -gt $max_minor ]] && max_minor=$_minor + done + echo "${max_minor}" +} + +function get_max_minor_version { + _get_max_minor_version < <(list_release_branches "${version_major}") +} diff --git a/releases/bin/_releases_lib.sh b/releases/bin/_releases_lib.sh new file mode 100644 index 0000000000..31fc1eefaf --- /dev/null +++ b/releases/bin/_releases_lib.sh @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# +# Additional functionality for release related scripts that deal with +# Git tags/branches and versions inferred to/from those, based on `_lib.sh`. +# +# Includes worktree checks (non-dirty, upstream, etc). +# + +if [[ -z ${bin_dir} ]]; then + echo "bin_dir variable undefined, fix the issue in the calling script, aborting" > /dev/stderr + exit 1 +fi + +. "${bin_dir}/_lib.sh" + +branch_type= +version_major= +version_minor= +# shellcheck disable=SC2154 +if [[ "${current_branch}" == "${main_branch}" ]]; then + branch_type="main" +elif echo "${current_branch}" | grep --extended-regexp --quiet "${release_branch_regex}"; then + # shellcheck disable=SC2034 + version_major="$(major_version_from_branch_name "${current_branch}")" + version_minor="$(minor_version_from_branch_name "${current_branch}")" + # shellcheck disable=SC2034 + [[ "x" == "${version_minor}" ]] && branch_type="major" || branch_type="minor" +else + echo "Current branch '${current_branch}' must be either the main branch '${main_branch}' or a release branch following exactly the pattern '${release_branch_prefix}.', aborting" > /dev/stderr + exit 1 +fi + +if [[ -z ${upstream_name} ]]; then + echo "Current branch '${current_branch}' has no remote, aborting" > /dev/stderr + exit 1 +fi + +if [[ -n "$(git status --untracked-files=no --porcelain)" ]]; then + echo "Current worktree has uncommitted changes, aborting" > /dev/stderr + git status --untracked-files=no --porcelain > /dev/stderr + exit 1 +fi diff --git a/releases/bin/create-release-branch.sh b/releases/bin/create-release-branch.sh new file mode 100755 index 0000000000..839a21c94c --- /dev/null +++ b/releases/bin/create-release-branch.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# Create a new release version branch +# +# If called on the main branch, creates the release branch for the next major version. +# If called on a version branch, the tool errors out. + +set -e +bin_dir="$(dirname "$0")" +command_name="$(basename "$0")" +. "${bin_dir}/_releases_lib.sh" + +# shellcheck disable=SC2154 +echo "Version in version.txt is '$version_txt'" +# shellcheck disable=SC2154 +echo "Current branch is '${current_branch}'" +# shellcheck disable=SC2154 +echo "Release Git remote name '${upstream_name}'" +echo "" + +function usage { + cat << EOF +${command_name} [--major MAJOR_VERSION] [--minor MINOR_VERSION] [--commit GIT_COMMIT] [--recreate] [--dry-run] [--help | -h] + + Creates a new release branch using the pattern '${release_branch_prefix}/.'. + + The major and minor versions are determined from the current branch. + + When invoked from the main branch, a new major-version branch '${release_branch_prefix}/.x' will be created. + When invoked from a major-version branch, a new minor-version branch '${release_branch_prefix}/..x' will be created. + + Options: + --commit GIT_COMMIT + The Git commit to draft the release from. Defaults to the current HEAD. + --recreate + Recreates the draft release if it already exists. + --dry-run + Do not update Git. Gradle will publish to the local Maven repository, but still sign the artifacts. + -h --help + Print usage information. +EOF +} + +dry_run= +recreate= +create_from_commit="$(git rev-parse HEAD)" +while [[ $# -gt 0 ]]; do + case $1 in + --commit) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --commit, aborting" > /dev/stderr + exit 1 + fi + create_from_commit="$1" + shift + ;; + --recreate) + recreate=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_branch_name="" +# shellcheck disable=SC2154 +case "${branch_type}" in + "main") + max_major="$(get_max_major_version)" + if [[ $max_major -eq -1 ]]; then + echo "No major release branch found" + new_branch_name="${release_branch_prefix}0.x" + else + echo "Latest major release branch is for version ${max_major}.x" + new_branch_name="${release_branch_prefix}$(( max_major + 1 )).x" + fi + ;; + "major") + max_minor="$(get_max_minor_version)" + if [[ $max_minor -eq -1 ]]; then + echo "No minor release branch found for ${version_major}.x" + new_branch_name="${release_branch_prefix}${version_major}.0.x" + else + echo "Latest major release branch is for version ${max_major}.x" + new_branch_name="${release_branch_prefix}${version_major}.$(( max_minor + 1)).x" + fi + ;; + "minor") + echo "On a minor version branch, aborting" > /dev/stderr + exit 1 + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +if [[ -z ${new_branch_name} ]]; then + echo "Empty branch to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +do_recreate= +if [[ ${recreate} ]]; then + if list_release_branches "" | grep --quiet "${new_branch_name}"; then + do_recreate=1 + fi +fi + +echo "" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made" +else + echo "Non-dry run, will update Git" +fi + +echo "" +echo "New branch name: ${new_branch_name}" +echo "From commit: ${create_from_commit}" +echo "" +git log -n1 "${create_from_commit}" +echo "" + +[[ ${do_recreate} ]] && exec_process "${dry_run}" git branch -D "${new_branch_name}" +exec_process "${dry_run}" git checkout -b "${new_branch_name}" "${create_from_commit}" + +[[ ${do_recreate} ]] && exec_process "${dry_run}" git push "${upstream_name}" --delete "${new_branch_name}" +exec_process "${dry_run}" git push --set-upstream "${upstream_name}" diff --git a/releases/bin/draft-release.sh b/releases/bin/draft-release.sh new file mode 100755 index 0000000000..816d789a80 --- /dev/null +++ b/releases/bin/draft-release.sh @@ -0,0 +1,504 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -e +bin_dir="$(dirname "$0")" +command_name="$(basename "$0")" +. "${bin_dir}/_releases_lib.sh" + +# shellcheck disable=SC2154 +echo "Version in version.txt is '$version_txt'" +# shellcheck disable=SC2154 +echo "Current branch is '${current_branch}'" +# shellcheck disable=SC2154 +echo "Release Git remote name '${upstream_name}'" +echo "" + +function usage { + # shellcheck disable=SC2154 + cat << EOF +${command_name} + [--major MAJOR_VERSION] [--minor MINOR_VERSION] + [--label VERSION_LABEL] + [--commit GIT_COMMIT] + [--previous-version PREVIOUS_VERSION] + [--recreate] + [--dry-run] + [--keep-release-worktree] + [--help | -h] + + Creates a release candidate. + + A new release candidate tag is created for the latest patch version for the major/minor version. + If no RC tag (following the '${tag_prefix}..-rcx' pattern) exists, an RC1 + will be created, otherwise the RC number will be incremented. If the latest patch version is + already promoted to GA, an RC1 for the next patch version will be created. + + Performs the Maven artifacts publication, Apache source tarball upload. Artifacts are signed, make + sure to have a compatible GPG key present. + + Release notes are generated via the external generate-release-notes.sh script, which can also be + invoked independently for development and testing purposes. + + Options: + --major MAJOR_VERSION + Major version number, must be specified when the command is called on the main branch. + --minor MINOR_VERSION + Minor version number, must be specified when the command is called on the main branch + or on a major version branch. + --label VERSION_LABEL + Additional label to add to the version. For example, specifying '--label beta' results in a version + like '1.2.3-incubating-beta' and corresponding RC tags '1.2.3-incubating-beta-rc42'. This parameter + is only needed for the first RC. Followup-RCs will fall back to the label from the previous RC. + The '-incubating' label part will automatically be prepended, do NOT specify it. + --commit GIT_COMMIT + The Git commit to draft the release from. Defaults to the current HEAD. + --previous-version PREVIOUS_VERSION + The full (major.minor.patch-labels) version _including_ the full version label to use as the base to + collect commits and contributor information for the release notes file. Examples: + 0.9.0-incubating + 0.10.0-incubating-beta + --recreate + Recreates the draft release if it already exists, to _replace_ an existing RC / reuse the RC iteration. + This should only be used in exceptional cases and never in production. + --dry-run + Do not update Git. Gradle will publish to the local Maven repository, but still sign the artifacts. + --keep-release-worktree + Keep the temporary Git worktree that is used to draft the release (in build/releases-git-worktree). + This is useful to inspect the results. Default behavior is to purge the temporary Git worktree. + -h --help + Print usage information. +EOF +} + +dry_run= +recreate= +keep_release_worktree= +# shellcheck disable=SC2154 +new_major_version="${version_major}" +# shellcheck disable=SC2154 +new_minor_version="${version_minor}" +new_version_label="" +current_head_commit="$(git rev-parse HEAD)" +create_from_commit="${current_head_commit}" +while [[ $# -gt 0 ]]; do + case $1 in + --major) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --major, aborting" > /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --label) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --label, aborting" > /dev/stderr + exit 1 + fi + new_version_label="-$1" + shift + ;; + --commit) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --commit, aborting" > /dev/stderr + exit 1 + fi + create_from_commit="$1" + shift + ;; + --previous-version) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --previous-version, aborting" > /dev/stderr + exit 1 + fi + release_notes_previous_version_full="$1" + shift + ;; + --recreate) + recreate=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --keep-release-worktree) + keep_release_worktree=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_tag_name="" +# shellcheck disable=SC2154 +case "${branch_type}" in + "main") + if [[ -z ${new_major_version} || -z ${new_minor_version} ]]; then + echo "On the main branch, but specified no major and/or minor version using the '--major'/'--minor' arguments, aborting" > /dev/stderr + exit 1 + fi + ;; + "major") + if [[ ${version_major} -ne ${new_major_version} ]]; then + echo "On the major version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the major version branch ${version_major}, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "minor") + if [[ ${version_major} -ne ${new_major_version} || ${version_minor} -ne ${new_minor_version} ]]; then + echo "On the minor version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +max_patch="$(get_max_patch_version "${new_major_version}" "${new_minor_version}")" +patch_version= +rc_iteration= +if [[ $max_patch -eq -1 ]]; then + # No previous patch release + patch_version=0 + rc_iteration=1 + new_version_label="$(get_podling_version_suffix)${new_version_label}" +else + latest_rc_tag="$(get_latest_rc_tag_name "${new_major_version}.${new_minor_version}.${max_patch}")" + if [[ -z ${latest_rc_tag} ]]; then + # that patch version is released, increase patch version + patch_version="$(( max_patch + 1))" + rc_iteration=1 + new_version_label="$(get_podling_version_suffix)${new_version_label}" + else + max_rc="$(rc_iteration_from_tag "${latest_rc_tag}")" + if [[ -z ${new_version_label} ]]; then + new_version_label="$(version_label_from_rc_tag "${latest_rc_tag}")" + fi + patch_version="${max_patch}" + rc_iteration="$(( max_rc + 1 ))" + fi +fi + +version_full="${new_major_version}.${new_minor_version}.${patch_version}${new_version_label}" +new_tag_name="${tag_prefix}${version_full}-rc${rc_iteration}" + +echo "" +gradleDryRunSigning="" +gradleReleaseArgs="" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made" + + # Only sign + use the GPG agent locally + [[ ${CI} ]] || gradleDryRunSigning="-PsignArtifacts -PuseGpgAgent" +else + echo "Non-dry run, will update Git" + + # Verify that the required secrets for the Maven publication are present. + if [[ ${CI} ]]; then + # Only publish to "Apache" from CI + gradleReleaseArgs="-Prelease publishToApache closeApacheStagingRepository" + + if [[ -z ${ORG_GRADLE_PROJECT_signingKey} || -z ${ORG_GRADLE_PROJECT_signingPassword} || -z ${ORG_GRADLE_PROJECT_sonatypeUsername} || -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] ; then + echo "One or more of the following required environment variables are missing:" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_signingKey} ]] && echo " ORG_GRADLE_PROJECT_signingKey" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_signingPassword} ]] && echo " ORG_GRADLE_PROJECT_signingPassword" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} ]] && echo " ORG_GRADLE_PROJECT_sonatypeUsername" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] && echo " ORG_GRADLE_PROJECT_sonatypePassword" > /dev/stderr + exit 1 + fi + else + # Only publish to "Apache" from CI, otherwise publish to local Maven repo + gradleReleaseArgs="-PsignArtifacts -PuseGpgAgent publishToMavenLocal" + fi +fi + +echo "" +echo "New version is: ${version_full}" +echo "RC iteration: ${rc_iteration}" +echo "New tag name is: ${new_tag_name}" +echo "From commit: ${create_from_commit}" +echo "Release-scripts: ${current_head_commit}" +echo "" +git log -n1 "${create_from_commit}" +echo "" + +if [[ -z ${new_tag_name} ]]; then + echo "Empty tag to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +existing_tag_name= +if [[ ${recreate} ]]; then + existing_tag_name="$(list_release_tags "" "" | grep "refs/tags/${tag_prefix}${new_major_version}.${new_minor_version}.${patch_version}.*-rc${rc_iteration}" | cut -f2 | cut -d/ -f3)" +fi + + + +start_group "Detach Git worktree" +# shellcheck disable=SC2154 +cd "${root_dir}" +# shellcheck disable=SC2154 +rm -rf "${worktree_dir}" +git worktree remove "${worktree_dir}" || true +git worktree add "${worktree_dir}" "${create_from_commit}" +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" + +if [[ ! ${keep_release_worktree} ]]; then + function purge_releases_git_worktree() { + echo "Purging directory $(pwd)" + rm -rf "${worktree_dir}" + git worktree remove "${worktree_dir}" || true + } + trap purge_releases_git_worktree EXIT +fi + +end_group + + + +start_group "Update version.txt" +echo "Executing 'echo -n \"${version_full}\" > version.txt'" +echo -n "${version_full}" > version.txt +exec_process 0 git add version.txt +end_group + + + +start_group "Create release notes" +releaseNotesFile="releases/current-release-notes.md" +mkdir -p releases +echo "Release notes file: ${releaseNotesFile}" +echo "Executing 'releases/bin/generate-release-notes.sh --major ${new_major_version} --minor ${new_minor_version} --patch ${patch_version} --label ${new_version_label} --previous ${release_notes_previous_version_full}'" +"${root_dir}/releases/bin/generate-release-notes.sh" --major "${new_major_version}" --minor "${new_minor_version}" --patch "${patch_version}" --label "${new_version_label}" --previous "${release_notes_previous_version_full}" > "${releaseNotesFile}" +exec_process 0 git add "${releaseNotesFile}" +end_group + + + +start_group "Gradle publication" +stagingRepositoryId="DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY ID!" +stagingRepositoryUrl="DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY URL!" +if [[ ${dry_run} ]]; then + # shellcheck disable=SC2086 + exec_process 0 ./gradlew clean publishToMavenLocal sourceTarball -PjarWithGitInfo ${gradleDryRunSigning} --stacktrace +else + # shellcheck disable=SC2086 + exec_process "${dry_run}" ./gradlew clean ${gradleReleaseArgs} sourceTarball --stacktrace | tee build/gradle-release-build.log + if [[ ${CI} ]]; then + # Extract staging repository ID from log (only do this in CI) + # ... look for the log message similar to 'Created staging repository 'orgprojectnessie-1214' at https://oss.sonatype.org/service/local/repositories/orgprojectnessie-1214/content/' + stagingLogMsg="$(grep 'Created staging repository' build/gradle-release-build.log)" + stagingRepositoryId="$(echo "$stagingLogMsg" | sed --regexp-extended "s/^Created staging repository .([a-z0-9-]+). at (.*)/\1/")" + stagingRepositoryUrl="$(echo "$stagingLogMsg" | sed --regexp-extended "s/^Created staging repository .([a-z0-9-]+). at (.*)/\2/")" + fi +fi +# Memoize the commit-ID, staging-repository-ID+URL for later use and reference information +# The staging-repository-ID is required for the 'publish-release.sh' script to release the staging repository. +echo -n "${create_from_commit}" > releases/current-release-commit-id +echo -n "${stagingRepositoryId}" > releases/current-release-staging-repository-id +echo -n "${stagingRepositoryUrl}" > releases/current-release-staging-repository-url +exec_process 0 git add releases/current-release-* +end_group + + + +start_group "Validate source tarball" +cd "${worktree_dir}" +cd build/distributions/ +shasum -a 512 -c "apache-polaris-$(cat "${worktree_dir}/version.txt").tar.gz.sha512" +if [[ ! -f "apache-polaris-$(cat "${worktree_dir}/version.txt").tar.gz.asc" ]]; then + echo "Signature file apache-polaris-$(cat "${worktree_dir}/version.txt").tar.gz.asc does not exist!" > /dev/stderr + exit 1 +fi +end_group + + + +start_group "Upload source tarball" +cd "${worktree_dir}" +# shellcheck disable=SC2154 +rm -rf "${svn_dir_dev}" +mkdir -p "${svn_dir_dev}" +cd "${svn_dir_dev}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + # shellcheck disable=SC2154 + echo "Using local, temporary svn for 'dev' for dry-run mode - as a local replacement for ${svn_dist_dev_repo}" + svn_local_dummy_dev="$(realpath ../svn-local-dummy-dev)" + if [[ ! -d ${svn_local_dummy_dev} ]] ; then + exec_process 0 svnadmin create "${svn_local_dummy_dev}" + fi + exec_process 0 svn checkout "file://${svn_local_dummy_dev}" . +else + exec_process 0 svn checkout "${svn_dist_dev_repo}" . +fi + +if [[ -d "${version_full}" ]]; then + # Delete previous RC iterations + exec_process 0 svn rm --force "${version_full}" + exec_process 0 svn commit -m "Remove previous Polaris ${version_full} release candidate(s)" +fi +svn_rc_path="${version_full}/RC${rc_iteration}" +mkdir -p "${svn_rc_path}" + +echo "" +find ../distributions/ +echo "" + +# We can safely assume that the Gradle 'sourceTarball' task leaves only the relevant files in +# build/distributions and that no other files are present +cp ../distributions/* "${svn_rc_path}" +exec_process 0 svn add --force "${version_full}" "${svn_rc_path}" +exec_process 0 svn commit -m "Polaris ${version_full} release candidate ${rc_iteration}" + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + +start_group "Commit changes to Git" +exec_process 0 git commit -m "[RELEASE] Version ${version_full}-rc${rc_iteration} + +Base release commit ID: ${create_from_commit} +Staging repository ID: ${stagingRepositoryId} +Staging repository URL: ${stagingRepositoryUrl} +Release notes in this commit in file: ${releaseNotesFile} +Release scripts version: ${current_head_commit} +" +tag_commit_id="$(git rev-parse HEAD)" +end_group + + + +start_group "Create Git tag ${new_tag_name}" +[[ -n ${existing_tag_name} ]] && exec_process "${dry_run}" git tag -d "${existing_tag_name}" +exec_process "${dry_run}" git tag "${new_tag_name}" "${tag_commit_id}" +end_group + + + +start_group "Release vote email" +echo "" +echo "Suggested release vote email subject:" +echo "=====================================" +echo "" +echo "[VOTE] Release Apache Polaris (Incubating) ${version_full}-rc${rc_iteration}" +echo "" +echo "" +echo "" +echo "Suggested Release vote email body:" +echo "==================================" +echo "" +# shellcheck disable=SC2154 +cat << EOF +Hi everyone, + +I propose that we release the following RC as the official +Apache Polaris (Incubating) ${version_full} release. + +The commit ID is ${tag_commit_id} +* This corresponds to the tag: ${new_tag_name} +* https://github.com/apache/polaris/commits/${new_tag_name} +* https://github.com/apache/polaris/tree/${tag_commit_id} + +The release tarball, signature, and checksums are here: +* ${svn_dist_dev_repo}/${svn_rc_path}/ + +You can find the KEYS file here: +* ${svn_dist_release_repo}/KEYS + +Convenience binary artifacts are staged on Nexus. The Maven repository URL is: +* ${stagingRepositoryUrl} + +Please download, verify, and test. + +Please vote in the next 72 hours. + +[ ] +1 Release this as Apache Polaris ${version_full} +[ ] +0 +[ ] -1 Do not release this because... + +Only PPMC members and mentors have binding votes, but other community members +are encouraged to cast non-binding votes. This vote will pass, if there are +3 binding +1 votes and more binding +1 votes than -1 votes. + +NB: if this vote pass, a new vote has to be started on the Incubator general mailing +list. + +Thanks +Regards +EOF +echo "" +echo "" +end_group + + + +start_group "Push Git tag ${new_tag_name}" +[[ -n ${existing_tag_name} ]] && exec_process "${dry_run}" git push "${upstream_name}" --delete "${existing_tag_name}" +exec_process "${dry_run}" git push "${upstream_name}" "${new_tag_name}" +end_group + + + +echo "" +if [[ ${dry_run} ]]; then + echo "***********************************" + echo "Draft-release finished successfully - but dry run was enabled, no changes were made to Git or SVN" + echo "***********************************" +else + echo "***********************************" + echo "Draft-release finished successfully" + echo "***********************************" +fi +echo "" +echo "" diff --git a/releases/bin/generate-release-notes.sh b/releases/bin/generate-release-notes.sh new file mode 100755 index 0000000000..d506f6d864 --- /dev/null +++ b/releases/bin/generate-release-notes.sh @@ -0,0 +1,224 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -e +bin_dir="$(dirname "$0")" +# shellcheck disable=SC2034 +command_name="$(basename "$0")" +. "${bin_dir}/_lib.sh" + +new_major_version="" +new_minor_version="" +patch_version="" +new_version_label="" +prev_version_full="" + +function usage { + cat < --minor --patch --label --previous [] + + major-version Major version being released + minor-version Minor version being released + patch-version Patch version being released + version-labels FULL version labels string, including leading '-', including leading '-incubating' + Examples: + -incubating + -incubating-beta + previous-version-full Optional: a different major.minor.patch version of the PREVIOUS release + Examples: + 0.9.0-incubating + 0.10.0-incubating-beta + +If 'previous-version-full' is not given, it will be inferred from the new major/minor/patch version. +For a patch-version grater than 0, it will be new-patch-version minus 1. +For a minor-version grater than 0, it will be new-minor-version minus 1 and patch-version 0. +For a major-version grater than 0, it will be new-major-version minus 1 and minor-version 0 and patch-version 0. +! +} + +while [[ $# -gt 0 ]]; do + case $1 in + --major) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --major, aborting" > /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --patch) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --patch, aborting" > /dev/stderr + exit 1 + fi + patch_version="$1" + shift + ;; + --label) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --label, aborting" > /dev/stderr + exit 1 + fi + new_version_label="$1" + shift + ;; + --previous) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --previous, aborting" > /dev/stderr + exit 1 + fi + prev_version_full="$1" + shift + ;; + *) + usage > /dev/stderr + exit 1 + ;; + esac +done + +if [[ -z ${new_major_version} ]]; then + echo "Mandatory --major option missing, aborting" > /dev/stderr + exit 1 +fi +if [[ -z ${new_minor_version} ]]; then + echo "Mandatory --minor option missing, aborting" > /dev/stderr + exit 1 +fi +if [[ -z ${patch_version} ]]; then + echo "Mandatory --patch option missing, aborting" > /dev/stderr + exit 1 +fi + +if [[ -z ${prev_version_full} ]]; then + if [[ ${patch_version} -gt 0 ]] ; then + prev_version_full="${new_major_version}.${new_minor_version}.$((patch_version - 1))" + elif [[ ${new_minor_version} -gt 0 ]] ; then + prev_version_full="${new_major_version}.$((new_minor_version - 1)).0" + elif [[ ${new_minor_version} -gt 0 ]] ; then + prev_version_full="$((new_major_version - 1)).0.0" + else + prev_version_full="0.0.0" + fi +fi + +prev_tag="$(tag_for_full_version "${prev_version_full}")" +prev_version_full="$(full_version_with_label_from_release_tag "${prev_tag}")" + +version_full="${new_major_version}.${new_minor_version}.${patch_version}${new_version_label}" + +total_contributor_count="$(curl https://api.github.com/repos/apache/polaris/contributors?per_page=5000 2>/dev/null | jq -r .[].login | grep --invert-match --extended-regexp "^(dependabot|renovate|sfc-).*" | sort | wc -l )" + +# shellcheck disable=SC2154 +cat < /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --keep-release-worktree) + keep_release_worktree=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_tag_name="" +from_tag_name="" +# shellcheck disable=SC2154 +case "${branch_type}" in + "main") + if [[ -z ${new_major_version} ]]; then + echo "On the main branch, but specified no major version using the '--major' argument, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the main branch, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "major") + if [[ ${version_major} -ne ${new_major_version} ]]; then + echo "On the major version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the major version branch ${version_major}, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "minor") + if [[ ${version_major} -ne ${new_major_version} || ${version_minor} -ne ${new_minor_version} ]]; then + echo "On the minor version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +patch_version="$(get_max_patch_version "${new_major_version}" "${new_minor_version}")" +rc_iteration= +from_tag_name= +if [[ $patch_version -eq -1 ]]; then + # that patch version is released + echo "Version ${new_major_version}.${new_minor_version}.x has no drafted patch release, aborting" > /dev/stderr + exit 1 +else + version_full_base="${new_major_version}.${new_minor_version}.${patch_version}" + from_tag_name="$(get_latest_rc_tag_name "${version_full_base}")" + if [[ -z ${from_tag_name} ]]; then + # that patch version is released + echo "Version ${version_full} is already released, aborting" > /dev/stderr + exit 1 + fi +fi +version_label="$(version_label_from_rc_tag "${from_tag_name}")" +version_full="${version_full_base}${version_label}" +# shellcheck disable=SC2154 +from_tag_name="${tag_prefix}${version_full}-rc${rc_iteration}" +new_tag_name="${tag_prefix}${version_full}" + +echo "" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made - except the versioned docs updates for local inspection" +else + echo "Non-dry run, will update Git" +fi + + +echo "" +echo "From tag name is: ${from_tag_name}" +echo "New tag name is: ${new_tag_name}" +echo "" +git log -n1 "${from_tag_name}" +echo "" + + +start_group "Detach Git worktree" +# shellcheck disable=SC2154 +cd "${root_dir}" +echo "Changed to directory $(pwd)" +# shellcheck disable=SC2154 +rm -rf "${worktree_dir}" +git worktree remove "${worktree_dir}" || true +git worktree add "${worktree_dir}" "${from_tag_name}" +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" + +if [[ ! ${keep_release_worktree} ]]; then + function purge_releases_git_worktree() { + echo "Purging directory $(pwd)" + rm -rf "${worktree_dir}" + git worktree remove "${worktree_dir}" || true + } + trap purge_releases_git_worktree EXIT +fi + +end_group + + +if [[ -z ${new_tag_name} || -z ${from_tag_name} ]]; then + echo "Empty tag to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +exec_process 0 git checkout "${from_tag_name}" + + + +start_group "Upload source tarball" +cd "${worktree_dir}" +# shellcheck disable=SC2154 +rm -rf "${svn_dir_dev}" +mkdir -p "${svn_dir_dev}" +cd "${svn_dir_dev}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + # shellcheck disable=SC2154 + echo "Using local, temporary svn for 'dev' for dry-run mode - as a local replacement for ${svn_dist_dev_repo}" + svn_local_dummy_dev="$(realpath ../svn-local-dummy-dev)" + if [[ ! -d ${svn_local_dummy_dev} ]] ; then + echo "Expecting existing local dummy svn for 'dev' for dry-run mode, created by dry-run draft-release.sh" > /dev/stderr + exit 1 + fi + exec_process 0 svn checkout "file://${svn_local_dummy_dev}" . +else + exec_process 0 svn checkout "${svn_dist_dev_repo}" . +fi + +cd "${worktree_dir}" +# shellcheck disable=SC2154 +rm -rf "${svn_dir_release}" +mkdir -p "${svn_dir_release}" +cd "${svn_dir_release}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + # shellcheck disable=SC2154 + echo "Using local, temporary svn for 'release' for dry-run mode - as a local replacement for ${svn_dist_release_repo}" + svn_local_dummy_release="$(realpath ../svn-local-dummy-release)" + if [[ ! -d ${svn_local_dummy_release} ]]; then + exec_process 0 svnadmin create "${svn_local_dummy_release}" + fi + exec_process 0 svn checkout "file://${svn_local_dummy_release}" . +else + exec_process 0 svn checkout "${svn_dist_release_repo}" . +fi + +if [[ -d "${version_full}" ]]; then + # Delete previous patch versions + find "${new_major_version}.${new_minor_version}.*" -type f -exec svn rm --force {} + +fi +mkdir -p "${version_full}" +# Copy the source tarball files from the release candidate +cp "${worktree_dir}/${svn_dir_dev}/${version_full}/RC${rc_iteration}"/* "${version_full}" +exec_process 0 svn add --force "${version_full}" +exec_process 0 svn commit -m "Polaris ${version_full} release" + +cd "${worktree_dir}/${svn_dir_dev}" +echo "Changed to directory $(pwd)" +exec_process 0 svn rm --force "${version_full}" +exec_process 0 svn commit -m "Remove RC${rc_iteration} for published Polaris ${version_full} release" + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + +start_group "Releasing staging repository" +if [[ ${CI} ]]; then + + if [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} || -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] ; then + echo "One or more of the following required environment variables are missing:" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} ]] && echo " ORG_GRADLE_PROJECT_sonatypeUsername" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] && echo " ORG_GRADLE_PROJECT_sonatypePassword" > /dev/stderr + exit 1 + fi + + stagingRepositoryId="$(cat releases/current-release-staging-repository-id)" + exec_process "${dry_run}" ./gradlew releaseApacheStagingRepository --staging-repository-id "${stagingRepositoryId}" --stacktrace +else + # Don't release anything when running locally, just print the statement + exec_process 1 ./gradlew releaseApacheStagingRepository --staging-repository-id "" --stacktrace +fi +end_group + + + +start_group "Create Git tag ${new_tag_name}" +exec_process "${dry_run}" git tag "${new_tag_name}" "${from_tag_name}" +exec_process "${dry_run}" git push "${upstream_name}" "${new_tag_name}" +end_group + + + +start_group "Generate release version docs" +cd "${worktree_dir}/site" +echo "Changed to directory $(pwd)" + +echo "Checking out released version docs..." +bin/remove-releases.sh --force > /dev/null || true +bin/checkout-releases.sh + +# Copy release docs content +rel_docs_dir="content/releases/${version_full}" +mkdir "${rel_docs_dir}" +echo "Copying docs from content/in-dev/unreleased to ${rel_docs_dir}..." +(cd content/in-dev/unreleased ; tar cf - .) | (cd "${rel_docs_dir}" ; tar xf -) + +echo "Copying release notes file (potentially replacing existing one)" +cp "${worktree_dir}/releases/current-release-notes.md" "${rel_docs_dir}/release-notes.md" + +# Copy release_index.md template as _index.md for the new release +versioned_docs_index_md="${rel_docs_dir}/_index.md" +echo "Updating released version ${versioned_docs_index_md}..." +had_marker= +while read -r ln ; do + if [[ "${ln}" == "# RELEASE_INDEX_MARKER_DO_NOT_CHANGE" ]]; then + had_marker=1 + cat << EOF +--- +$(cat ../codestyle/copyright-header-hash.txt) +title: 'Polaris v${version_full}' +date: $(date --iso-8601=seconds) +params: + release_version: "${version_full}" +cascade: + exclude_search: false +EOF + fi + [[ ${had_marker} ]] && echo "${ln}" +done < content/in-dev/release_index.md > "${versioned_docs_index_md}" +end_group + + + +start_group "Announcement email" +echo "" +echo "----------------------------------------------------------------------------------------------------------------" +echo "" +echo "Suggested announcement email subject:" +echo "=====================================" +echo "" +echo "[ANNOUNCE] Apache Polaris release ${version_full}" +echo "" +echo "" +echo "" +echo "Suggested announcement email body:" +echo "==================================" +echo "" +cat << EOF +Hi everyone, + +I'm pleased to announce the release of Apache Polaris ${version_full}! + +Apache Polaris is an open-source, fully-featured catalog for Apache Iceberg™. It implements Iceberg's REST API, +enabling seamless multi-engine interoperability across a wide range of platforms, including Apache Doris™, +Apache Flink®, Apache Spark™, StarRocks, and Trino. + +This release can be downloaded from: https://dlcdn.apache.org/polaris/apache-polaris-${version_full}/apache-polaris-${version_full}.tar.gz + +Release notes at https://polaris.apache.org/release/${version_full}/release-notes +and https://github.com/apache/polaris/releases/tag/${new_tag_name}. + +Docs for the ${version_full} release are on https://polaris.apache.org/release/${version_full} + +Java artifacts are available from Maven Central. + +Thanks to everyone for contributing! +EOF +echo "" +echo "" +echo "----------------------------------------------------------------------------------------------------------------" +echo "" +echo "" + +cd content/releases +echo "Changed to directory $(pwd)" + +exec_process "${dry_run}" git add . +exec_process "${dry_run}" git commit -m "Add versioned docs for release ${version_full}" +exec_process "${dry_run}" git push + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + + +start_group "GitHub release" +cd "${worktree_dir}" +if [[ ${CI} ]]; then + exec_process "${dry_run}" gh release create "${new_tag_name}" \ + --notes-file "${version_full}/release-notes.md" \ + --title "Apache Polaris ${version_full}" +else + # GitHub release only created from CI (dry-run always enabled locally) + exec_process 1 gh release create "${new_tag_name}" \ + --notes-file "${version_full}/release-notes.md" \ + --title "Apache Polaris ${version_full}" +fi +end_group + + + + +echo "" +if [[ ${dry_run} ]]; then + echo "*************************************" + echo "Publish-release finished successfully - but dry run was enabled, no changes were made to Git or SVN" + echo "*************************************" +else + echo "*************************************" + echo "Publish-release finished successfully" + echo "*************************************" +fi +echo "" +echo "" diff --git a/releases/test/.gitignore b/releases/test/.gitignore new file mode 100644 index 0000000000..7a2137132a --- /dev/null +++ b/releases/test/.gitignore @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +bats/ +test_helper/ diff --git a/releases/test/README.md b/releases/test/README.md new file mode 100644 index 0000000000..99ae19e7b8 --- /dev/null +++ b/releases/test/README.md @@ -0,0 +1,46 @@ + + +# Apache Polaris Release Infrastructure Tests + +## Prerequisites + +A GPG key usable for signing is required, and a GPG agent. + +See also [bats docs](https://bats-core.readthedocs.io/en/stable/index.html) + +## Setup + +```bash +cd releases/test + +./setup-tests.sh +``` + +Run the `export PATH=...` mentioned in the output. + +## Run tests + +```bash +bats test/ +``` + +## IntelliJ tip + +Tell IntelliJ to handle `*.bats` files as shell scripts. diff --git a/releases/test/setup-tests.sh b/releases/test/setup-tests.sh new file mode 100755 index 0000000000..7c8ab40e00 --- /dev/null +++ b/releases/test/setup-tests.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -e + +cd "${0%/*}" + +function setup_bats_repo() { + local repo="$1" + local dir="$2" + + + if [[ -d "${dir}" ]]; then + (cd "$dir" ; git pull) + else + mkdir -p "${dir}" + if [[ -n "${CI}" ]]; then + git clone --depth 1 "${repo}" "${dir}" + else + git clone "${repo}" "${dir}" + fi + fi +} + +setup_bats_repo https://github.com/bats-core/bats-core.git bats +setup_bats_repo https://github.com/bats-core/bats-support.git test_helper/bats-support +setup_bats_repo https://github.com/bats-core/bats-assert.git test_helper/bats-assert +setup_bats_repo https://github.com/bats-core/bats-file.git test_helper/bats-file + +echo "" +echo "" +echo "===============================================================================" +echo "Run the following before running any tests!" +echo " export PATH=$(realpath .)/bats/bin:\$PATH" +echo "===============================================================================" +echo "" diff --git a/releases/test/test_draft-release.bats b/releases/test/test_draft-release.bats new file mode 100644 index 0000000000..1bbdbb2366 --- /dev/null +++ b/releases/test/test_draft-release.bats @@ -0,0 +1,128 @@ +#!/usr/bin/env bats +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +setup() { + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/bats-file/load' + + # make the tested stuff visible to PATH + RELEASE_BIN="${BATS_TEST_DIRNAME}/../bin" + PRJ_ROOT="${BATS_TEST_DIRNAME}/../.." + PATH="${RELEASE_BIN}:$PATH" + + # shellcheck disable=SC2034 + svn_dir_prefix="${BATS_RUN_TMPDIR}/svn-dir" +} + +@test "draft-release help" { + run git status --untracked-files=no --porcelain + assert_output "" + + run draft-release.sh -h + assert_line "Version in version.txt is '$(cat "$PRJ_ROOT"/version.txt)'" + assert_line --partial "Current branch is '" + assert_line --partial "Release Git remote name '" + assert_success +} + +@test "draft-release dry-run" { + if [[ -n ${CI} ]]; then + skip "Test requires GPG signing, not available in CI" + fi + + run git status --untracked-files=no --porcelain + assert_output "" + + run draft-release.sh \ + --major 42 --minor 43 --label bats-testing \ + --previous-version 0.9.0-incubating \ + --commit 68fb898dfd9950cb312a26e923ab0f4e4f0cb6dc \ + --dry-run \ + --keep-release-worktree + + assert_line --partial "Version in version.txt is '" + + assert_line "Dry run, no changes will be made" + + assert_line "New version is: 42.43.0-incubating-bats-testing" + assert_line "RC iteration: 1" + assert_line "New tag name is: apache-polaris-42.43.0-incubating-bats-testing-rc1" + assert_line "From commit: 68fb898dfd9950cb312a26e923ab0f4e4f0cb6dc" + + assert_line --partial "Changed to directory " + + # Update version.txt + assert_line "Executing 'echo -n \"42.43.0-incubating-bats-testing\" > version.txt'" + assert_line "Executing 'git add version.txt'" + + # Create release notes + assert_line "Release notes file: releases/current-release-notes.md" + assert_line "Executing 'releases/bin/generate-release-notes.sh --major 42 --minor 43 --patch 0 --label -incubating-bats-testing --previous 0.9.0-incubating'" + assert_line "Executing 'git add releases/current-release-notes.md'" + + # Gradle publication + assert_line "Executing './gradlew clean publishToMavenLocal sourceTarball -PjarWithGitInfo -PsignArtifacts -PuseGpgAgent --stacktrace'" + + # Validate source tarball + assert_line "apache-polaris-42.43.0-incubating-bats-testing.tar.gz: OK" + + # Upload source tarball + assert_line "Executing 'svn add --force 42.43.0-incubating-bats-testing 42.43.0-incubating-bats-testing/RC1'" + assert_line "A 42.43.0-incubating-bats-testing" + assert_line "A 42.43.0-incubating-bats-testing/RC1" + assert_line "A (bin) 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz" + assert_line "A 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz.sha512" + assert_line "A 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz.asc" + assert_line "Executing 'svn commit -m Polaris 42.43.0-incubating-bats-testing release candidate 1'" + assert_line "Adding 42.43.0-incubating-bats-testing" + assert_line "Adding 42.43.0-incubating-bats-testing/RC1" + assert_line "Adding (bin) 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz" + assert_line "Adding 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz.asc" + assert_line "Adding 42.43.0-incubating-bats-testing/RC1/apache-polaris-42.43.0-incubating-bats-testing.tar.gz.sha512" + + # Commit changes to Git + assert_line "Executing 'git commit -m [RELEASE] Version 42.43.0-incubating-bats-testing-rc1" + + # Create Git tag apache-polaris-42.43.0-incubating-bats-testing-rc1 + assert_line --partial "Dry-run, WOULD execute 'git tag apache-polaris-42.43.0-incubating-bats-testing-rc1 " + + # Release vote mail subject + assert_line "[VOTE] Release Apache Polaris (Incubating) 42.43.0-incubating-bats-testing-rc1" + # Release vote mail content + assert_line "* This corresponds to the tag: apache-polaris-42.43.0-incubating-bats-testing-rc1" + assert_line "[ ] +1 Release this as Apache Polaris 42.43.0-incubating-bats-testing" + assert_success + + cd "${PRJ_ROOT}"/build/releases-git-worktree + run git log -n1 + + assert_line " [RELEASE] Version 42.43.0-incubating-bats-testing-rc1" + assert_line " Base release commit ID: 68fb898dfd9950cb312a26e923ab0f4e4f0cb6dc" + assert_line " Staging repository ID: DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY ID!" + assert_line " Staging repository URL: DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY URL!" + assert_line " Release notes in this commit in file: releases/current-release-notes.md" + assert_line --partial " Release scripts version: " + + assert_file_contains releases/current-release-notes.md "New commits in version 42.43.0-incubating-bats-testing since 0.9.0-incubating" + assert_file_contains releases/current-release-commit-id "68fb898dfd9950cb312a26e923ab0f4e4f0cb6dc" + assert_file_not_empty releases/current-release-staging-repository-id + assert_file_not_empty releases/current-release-staging-repository-url +} diff --git a/releases/test/test_lib.bats b/releases/test/test_lib.bats new file mode 100644 index 0000000000..906fe38a8b --- /dev/null +++ b/releases/test/test_lib.bats @@ -0,0 +1,350 @@ +#!/usr/bin/env bats +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +setup() { + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/bats-file/load' + + # make the tested stuff visible to PATH + RELEASE_BIN="${BATS_TEST_DIRNAME}/../bin" + PRJ_ROOT="${BATS_TEST_DIRNAME}/../.." + PATH="${RELEASE_BIN}:$PATH" +} + +# shellcheck disable=SC2034 +@test "get_podling_version_suffix" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_lib.sh" + run get_podling_version_suffix + assert_output "-incubating" + assert_success +} + +# shellcheck disable=SC2034 +@test "list_release_branches" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + run list_release_branches "" "" + assert_line "release/0.9.x" + assert_success +} + +# shellcheck disable=SC2034 +@test "list_release_tags no prefix" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + run list_release_tags "" + assert_line "apache-polaris-0.9.0-incubating" + assert_line "apache-polaris-0.9.0-incubating-rc1" + assert_line "apache-polaris-0.9.0-incubating-rc6" + assert_success +} + +# shellcheck disable=SC2034 +@test "major_minor_version_from_branch_name" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run major_version_from_branch_name "release/0.9.x" + assert_output "0" + assert_success + + run minor_version_from_branch_name "release/0.9.x" + assert_output "9" + assert_success + + run major_version_from_branch_name "release/0.9" + assert_output "0" + assert_success + + run minor_version_from_branch_name "release/0.9" + assert_output "9" + assert_success + + run major_version_from_branch_name "release/1.2.x" + assert_output "1" + assert_success + + run minor_version_from_branch_name "release/1.2.x" + assert_output "2" + assert_success + + run major_version_from_branch_name "release/1.2" + assert_output "1" + assert_success + + run minor_version_from_branch_name "release/1.2" + assert_output "2" + assert_success +} + +# shellcheck disable=SC2034 +@test "get_max_rc_iteration" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run _get_max_rc_iteration << EOF +apache-polaris-0.9.0-incubating-rc1 +apache-polaris-0.9.0-incubating-rc2 +apache-polaris-0.9.0-incubating-rc3 +EOF + assert_output "3" + assert_success + + run _get_max_rc_iteration << EOF +apache-polaris-0.9.0-incubating +apache-polaris-0.9.0-incubating-rc1 +apache-polaris-0.9.0-incubating-rc2 +apache-polaris-0.9.0-incubating-rc3 +EOF + assert_output "-2" # marker, that 0.9.0-incubating is released + assert_success +} + +# shellcheck disable=SC2034 +@test "get_max_patch_version" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run _get_max_patch_version << EOF +apache-polaris-0.42.0-incubating +apache-polaris-0.42.0-incubating-rc1 +apache-polaris-0.42.7-incubating +apache-polaris-0.42.7-incubating-rc1 +apache-polaris-0.42.7-incubating-rc2 +apache-polaris-0.42.3-incubating +apache-polaris-0.42.3-incubating-rc1 +apache-polaris-0.42.3-incubating-rc2 +EOF + assert_output "7" + assert_success +} + +# shellcheck disable=SC2034 +@test "tag_for_full_version" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run _tag_for_full_version << EOF +apache-polaris-0.42.0-incubating +apache-polaris-0.42.0-incubating-rc1 +apache-polaris-0.42.0-incubating-rc3 +EOF + assert_output "apache-polaris-0.42.0-incubating" + assert_success + + run _tag_for_full_version << EOF +apache-polaris-0.42.0-incubating-rc3 +apache-polaris-0.42.0-incubating +apache-polaris-0.42.0-incubating-rc1 +EOF + assert_output "apache-polaris-0.42.0-incubating" + assert_success + + run _tag_for_full_version << EOF +apache-polaris-0.42.0-incubating +apache-polaris-0.42.0-incubating-rc1 +apache-polaris-0.42.0-incubating-rc3 +EOF + assert_output "apache-polaris-0.42.0-incubating" + assert_success +} + +# shellcheck disable=SC2034 +@test "get_latest_rc_tag_name" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run _get_latest_rc_tag_name << EOF +apache-polaris-0.42.3-incubating-rc1 +apache-polaris-0.42.3-incubating-rc13 +EOF + assert_output "apache-polaris-0.42.3-incubating-rc13" + assert_success + + run _get_latest_rc_tag_name << EOF +apache-polaris-0.42.3-incubating +apache-polaris-0.42.3-incubating-rc1 +apache-polaris-0.42.3-incubating-rc13 +EOF + assert_output "" + assert_success +} + +# shellcheck disable=SC2034 +@test "full_version_with_label_from_release_tag" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run full_version_with_label_from_release_tag "apache-polaris-1.2.3-incubating-beta" + assert_output "1.2.3-incubating-beta" + assert_success + + run full_version_with_label_from_release_tag "apache-polaris-1.2.3-incubating" + assert_output "1.2.3-incubating" + assert_success + + run full_version_with_label_from_release_tag "apache-polaris-1.2.3-beta" + assert_output "1.2.3-beta" + assert_success + + run full_version_with_label_from_release_tag "apache-polaris-1.2.3" + assert_output "1.2.3" + assert_success +} + +# shellcheck disable=SC2034 +@test "patch_version_from_rc_tag" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run patch_version_from_rc_tag "apache-polaris-1.2.3-incubating-beta-rc42" + assert_output "3" + assert_success + + run patch_version_from_rc_tag "apache-polaris-1.2.3-incubating-rc42" + assert_output "3" + assert_success + + run patch_version_from_rc_tag "apache-polaris-1.2.3-beta-rc42" + assert_output "3" + assert_success + + run patch_version_from_rc_tag "apache-polaris-1.2.3-rc42" + assert_output "3" + assert_success + + run patch_version_from_rc_tag "apache-polaris-1.2.3-incubating-beta" + assert_output "" # not an RC tag + assert_failure + + run patch_version_from_rc_tag "apache-polaris-1.2.3-incubating" + assert_output "" # not an RC tag + assert_failure + + run patch_version_from_rc_tag "apache-polaris-1.2.3-beta" + assert_output "" # not an RC tag + assert_failure + + run patch_version_from_rc_tag "apache-polaris-1.2.3" + assert_output "" # not an RC tag + assert_failure +} + +# shellcheck disable=SC2034 +@test "rc_iteration_from_tag" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run rc_iteration_from_tag "apache-polaris-1.2.3-incubating-beta-rc42" + assert_output "42" + assert_success + + run rc_iteration_from_tag "apache-polaris-1.2.3-incubating-rc42" + assert_output "42" + assert_success + + run rc_iteration_from_tag "apache-polaris-1.2.3-beta-rc42" + assert_output "42" + assert_success + + run rc_iteration_from_tag "apache-polaris-1.2.3-rc42" + assert_output "42" + assert_success + + run rc_iteration_from_tag "apache-polaris-1.2.3-incubating-beta" + assert_output "" # not an RC tag + assert_failure + + run rc_iteration_from_tag "apache-polaris-1.2.3-incubating" + assert_output "" # not an RC tag + assert_failure + + run rc_iteration_from_tag "apache-polaris-1.2.3-beta" + assert_output "" # not an RC tag + assert_failure + + run rc_iteration_from_tag "apache-polaris-1.2.3" + assert_output "" # not an RC tag + assert_failure +} + +# shellcheck disable=SC2034 +@test "version_label_from_rc_tag" { + bin_dir="${RELEASE_BIN}" + command_name="test_libs" + + . "$RELEASE_BIN/_releases_lib.sh" + + run version_label_from_rc_tag "apache-polaris-1.2.3-incubating-beta" + assert_output "" # not an RC tag + assert_failure + + run version_label_from_rc_tag "apache-polaris-1.2.3-incubating" + assert_output "" # not an RC tag + assert_failure + + run version_label_from_rc_tag "apache-polaris-1.2.3-beta" + assert_output "" # not an RC tag + assert_failure + + run version_label_from_rc_tag "apache-polaris-1.2.3" + assert_output "" # not an RC tag + assert_failure + + run version_label_from_rc_tag "apache-polaris-1.2.3-incubating-beta-rc42" + assert_output "-incubating-beta" + assert_success + + run version_label_from_rc_tag "apache-polaris-1.2.3-incubating-rc42" + assert_output "-incubating" + assert_success + + run version_label_from_rc_tag "apache-polaris-1.2.3-beta-rc42" + assert_output "-beta" + assert_success + + run version_label_from_rc_tag "apache-polaris-1.2.3-rc42" + assert_output "" + assert_success +} diff --git a/site/bin/checkout-releases.sh b/site/bin/checkout-releases.sh index 13bb1b318a..610e89ed36 100755 --- a/site/bin/checkout-releases.sh +++ b/site/bin/checkout-releases.sh @@ -25,7 +25,7 @@ cd "$(dirname "$0")/.." git worktree prune if [[ -d content/releases ]] ; then - echo "Directory content/releases already exists" + echo "Directory content/releases already exists" > /dev/stderr exit 1 fi diff --git a/site/bin/remove-releases.sh b/site/bin/remove-releases.sh index 4a067d0384..c8308aaa7c 100755 --- a/site/bin/remove-releases.sh +++ b/site/bin/remove-releases.sh @@ -22,17 +22,36 @@ set -e cd "$(dirname "$0")/.." -if [[ ! -d content/releases ]] ; then - echo "Directory content/releases does not exists" - exit 1 -fi +force= +while [[ $# -gt 0 ]]; do + case $1 in + --force) + force=1 + shift + ;; + *) + echo "Unexpected argument '$1'" > /dev/stderr + exit 1 + ;; + esac +done -cd content/releases -if git diff-index --quiet HEAD -- ; then - cd ../.. +if [[ ${force} ]] ; then rm -rf content/releases git worktree prune else - echo "Directory content/releases contains uncommitted changes" - exit 1 + if [[ ! -d content/releases ]] ; then + echo "Directory content/releases does not exists" > /dev/stderr + exit 1 + fi + + cd content/releases + if git diff-index --quiet HEAD -- ; then + cd ../.. + rm -rf content/releases + git worktree prune + else + echo "Directory content/releases contains uncommitted changes" > /dev/stderr + exit 1 + fi fi diff --git a/site/content/in-dev/release_index.md b/site/content/in-dev/release_index.md index e8f38dbfaa..59fb8f3fed 100644 --- a/site/content/in-dev/release_index.md +++ b/site/content/in-dev/release_index.md @@ -28,8 +28,11 @@ cascade: params: show_page_toc: true # This file will be copied as `_index.md` into a new release's versioned docs folder. +# RELEASE_INDEX_MARKER_DO_NOT_CHANGE --- -== Apache Polaris version {{< releaseVersion >}} +## Apache Polaris version {{< releaseVersion >}} + +[Release notes](./release-notes) can be found [here](./release-notes) Download from ...