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 ...