From 8b7f0b9e52a3353fd9f8dc5f2664ab4f27a8977f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Cumplido?= Date: Thu, 16 Apr 2026 11:47:21 +0200 Subject: [PATCH 1/2] GH-49743: [Release] Split the vote thread preparation into its own shell script --- dev/release/02-source.sh | 71 --------------- dev/release/09-vote-email.sh | 136 +++++++++++++++++++++++++++++ docs/source/developers/release.rst | 2 +- 3 files changed, 137 insertions(+), 72 deletions(-) create mode 100755 dev/release/09-vote-email.sh diff --git a/dev/release/02-source.sh b/dev/release/02-source.sh index a99e529065e6..1fe0adcac2ed 100755 --- a/dev/release/02-source.sh +++ b/dev/release/02-source.sh @@ -25,7 +25,6 @@ set -eu : ${SOURCE_RAT:=${SOURCE_DEFAULT}} : ${SOURCE_UPLOAD:=${SOURCE_DEFAULT}} : ${SOURCE_PR:=${SOURCE_DEFAULT}} -: ${SOURCE_VOTE:=${SOURCE_DEFAULT}} SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_TOP_DIR="$(cd "${SOURCE_DIR}/../../" && pwd)" @@ -114,73 +113,3 @@ if [ ${SOURCE_PR} -gt 0 ]; then --verify-source \ --version=${version} fi - -if [ ${SOURCE_VOTE} -gt 0 ]; then - curl_common_options=(--header "Authorization: Bearer ${GH_TOKEN}") - - curl_options=("${curl_common_options[@]}") - curl_options+=(--data "{\"query\": \"query {search(query: \\\"repo:apache/arrow is:issue is:closed milestone:${version}\\\", type:ISSUE) {issueCount}}\"}") - curl_options+=("https://api.github.com/graphql") - n_resolved_issues=$(curl "${curl_options[@]}" | jq ".data.search.issueCount") - - curl_options=("${curl_common_options[@]}") - curl_options+=(--header "Accept: application/vnd.github+json") - curl_options+=(--get) - curl_options+=(--data "state=open") - curl_options+=(--data "head=apache:${rc_branch}") - curl_options+=(https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls) - verify_pr_url=$(curl "${curl_options[@]}" | jq -r ".[0].html_url") - # Read the checksum so we can include it in the vote thread email. - sha512_path="artifacts/${tarball}.sha512" - [[ -f "${sha512_path}" ]] || { echo "Error: ${sha512_path} must exist"; exit 1; } - tarball_hash=$(cat "${sha512_path}" | awk '{print $1}') - - echo "The following draft email has been created to send to the" - echo "dev@arrow.apache.org mailing list" - echo "" - echo "---------------------------------------------------------" - cat < " + exit +fi + +version=$1 +rc=$2 + +. "${SOURCE_DIR}/utils-env.sh" + +tag=apache-arrow-${version}-rc${rc} +rc_branch="release-${version}-rc${rc}" +rc_url="https://dist.apache.org/repos/dist/dev/arrow/${tag}" + +: ${release_hash:=$(cd "${SOURCE_TOP_DIR}" && git rev-list --max-count=1 ${tag})} +: ${GITHUB_REPOSITORY:=apache/arrow} + +echo "Using commit $release_hash" + +tarball=apache-arrow-${version}.tar.gz + +if [ ${SOURCE_DOWNLOAD} -gt 0 ]; then + echo "Downloading tarball and checksums for ${tag}" + # Wait for the release candidate workflow to finish before attempting + # to download the tarball from the GitHub Release. + . $SOURCE_DIR/utils-watch-gh-workflow.sh ${tag} "release_candidate.yml" + rm -rf artifacts + gh release download ${tag} \ + --dir artifacts \ + --repo "${GITHUB_REPOSITORY}" \ + --pattern "${tarball}.*" +fi + +if [ ${SOURCE_VOTE} -gt 0 ]; then + curl_common_options=(--header "Authorization: Bearer ${GH_TOKEN}") + + curl_options=("${curl_common_options[@]}") + curl_options+=(--data "{\"query\": \"query {search(query: \\\"repo:apache/arrow is:issue is:closed milestone:${version}\\\", type:ISSUE) {issueCount}}\"}") + curl_options+=("https://api.github.com/graphql") + n_resolved_issues=$(curl "${curl_options[@]}" | jq ".data.search.issueCount") + + curl_options=("${curl_common_options[@]}") + curl_options+=(--header "Accept: application/vnd.github+json") + curl_options+=(--get) + curl_options+=(--data "state=open") + curl_options+=(--data "head=apache:${rc_branch}") + curl_options+=(https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls) + verify_pr_url=$(curl "${curl_options[@]}" | jq -r ".[0].html_url") + # Read the checksum so we can include it in the vote thread email. + sha512_path="artifacts/${tarball}.sha512" + [[ -f "${sha512_path}" ]] || { echo "Error: ${sha512_path} must exist"; exit 1; } + tarball_hash=$(cat "${sha512_path}" | awk '{print $1}') + + echo "The following draft email has been created to send to the" + echo "dev@arrow.apache.org mailing list" + echo "" + echo "---------------------------------------------------------" + cat < + dev/release/09-vote-email.sh See :ref:`release_verification` for details. From 0ab06ae3cf2265bb440ab0010035bd26a833eb40 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Fri, 17 Apr 2026 11:25:59 +0900 Subject: [PATCH 2/2] Adjust test --- dev/release/02-source-test.rb | 83 ------------------ dev/release/09-vote-email-test.rb | 136 ++++++++++++++++++++++++++++++ dev/release/09-vote-email.sh | 25 +++--- 3 files changed, 147 insertions(+), 97 deletions(-) create mode 100644 dev/release/09-vote-email-test.rb diff --git a/dev/release/02-source-test.rb b/dev/release/02-source-test.rb index fdd1db9c6072..bc97c43ab2d7 100644 --- a/dev/release/02-source-test.rb +++ b/dev/release/02-source-test.rb @@ -46,8 +46,6 @@ def source(*targets) sh(env, @tarball_script, @release_version, "0") FileUtils.mkdir_p("artifacts") sh("mv", @archive_name, "artifacts/") - File.write("artifacts/#{@archive_name}.sha512", - sh(env, "shasum", "-a", "512", "artifacts/#{@archive_name}")) output = sh(env, @script, @release_version, "0") sh("tar", "xf", "artifacts/#{@archive_name}") output @@ -74,85 +72,4 @@ def test_python_version Dir.glob("dist/pyarrow-*.tar.gz")) end end - - def test_vote - github_token = File.read(@env)[/^GH_TOKEN=(.*)$/, 1] - uri = URI.parse("https://api.github.com/graphql") - n_issues_query = { - "query" => <<-QUERY, - query { - search(query: "repo:apache/arrow is:issue is:closed milestone:#{@release_version}", - type: ISSUE) { - issueCount - } - } - QUERY - } - response = Net::HTTP.post(uri, - n_issues_query.to_json, - "Content-Type" => "application/json", - "Authorization" => "Bearer #{github_token}") - n_resolved_issues = JSON.parse(response.body)["data"]["search"]["issueCount"] - github_repository = ENV["GITHUB_REPOSITORY"] || "apache/arrow" - github_api_url = "https://api.github.com" - verify_prs = URI("#{github_api_url}/repos/#{github_repository}/pulls" + - "?state=open" + - "&head=apache:release-#{@release_version}-rc0") - verify_pr_url = nil - headers = { - "Accept" => "application/vnd.github+json", - } - - if github_token - headers["Authorization"] = "Bearer #{github_token}" - end - verify_prs.open(headers) do |response| - verify_pr_url = (JSON.parse(response.read)[0] || {})["html_url"] - end - output = source("VOTE") - tarball_hash = Digest::SHA512.file("artifacts/#{@archive_name}").to_s - assert_equal(<<-VOTE.strip, output[/^-+$(.+?)^-+$/m, 1].strip) -To: dev@arrow.apache.org -Subject: [VOTE] Release Apache Arrow #{@release_version} - RC0 - -Hi, - -I would like to propose the following release candidate (RC0) of Apache -Arrow version #{@release_version}. This is a release consisting of #{n_resolved_issues} -resolved GitHub issues[1]. - -This release candidate is based on commit: -#{@current_commit} [2] - -The source release rc0 is hosted at [3]. -The binary artifacts are hosted at [4][5][6][7][8][9]. -The changelog is located at [10]. - -Please download, verify checksums and signatures, run the unit tests, -and vote on the release. See [11] for the SHA-512 checksum for this RC and [12] -for how to validate a release candidate. - -See also a verification result on GitHub pull request [13]. - -The vote will be open for at least 72 hours. - -[ ] +1 Release this as Apache Arrow #{@release_version} -[ ] +0 -[ ] -1 Do not release this as Apache Arrow #{@release_version} because... - -[1]: https://github.com/apache/arrow/issues?q=is%3Aissue+milestone%3A#{@release_version}+is%3Aclosed -[2]: https://github.com/apache/arrow/tree/#{@current_commit} -[3]: https://dist.apache.org/repos/dist/dev/arrow/apache-arrow-#{@release_version}-rc0 -[4]: https://packages.apache.org/artifactory/arrow/almalinux-rc/ -[5]: https://packages.apache.org/artifactory/arrow/amazon-linux-rc/ -[6]: https://packages.apache.org/artifactory/arrow/centos-rc/ -[7]: https://packages.apache.org/artifactory/arrow/debian-rc/ -[8]: https://packages.apache.org/artifactory/arrow/ubuntu-rc/ -[9]: https://github.com/apache/arrow/releases/tag/apache-arrow-#{@release_version}-rc0 -[10]: https://github.com/apache/arrow/blob/#{@current_commit}/CHANGELOG.md -[11]: #{tarball_hash} -[12]: https://arrow.apache.org/docs/developers/release_verification.html -[13]: #{verify_pr_url || "null"} - VOTE - end end diff --git a/dev/release/09-vote-email-test.rb b/dev/release/09-vote-email-test.rb new file mode 100644 index 000000000000..bae314b24511 --- /dev/null +++ b/dev/release/09-vote-email-test.rb @@ -0,0 +1,136 @@ +# 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. + +class VoteEmailTest < Test::Unit::TestCase + include GitRunnable + include VersionDetectable + + def setup + @current_commit = git_current_commit + detect_versions + @tag_name_no_rc = "apache-arrow-#{@release_version}" + @archive_name = "apache-arrow-#{@release_version}.tar.gz" + @script = File.expand_path("dev/release/09-vote-email.sh") + @tarball_script = File.expand_path("dev/release/utils-create-release-tarball.sh") + @env = File.expand_path("dev/release/.env") + + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + yield + end + end + end + + def vote_email(*targets) + env = { + "VOTE_DEFAULT" => "0", + "release_hash" => @current_commit, + } + targets.each do |target| + env["VOTE_#{target}"] = "1" + end + sh(env, @tarball_script, @release_version, "0") + FileUtils.mkdir_p("artifacts") + sh("mv", @archive_name, "artifacts/") + Dir.chdir("artifacts") do + File.write("#{@archive_name}.sha512", + sh(env, "shasum", "-a", "512", @archive_name)) + end + sh(env, @script, @release_version, "0") + end + + def test_template + github_token = File.read(@env)[/^GH_TOKEN=(.*)$/, 1] + uri = URI.parse("https://api.github.com/graphql") + n_issues_query = { + "query" => <<-QUERY, + query { + search(query: "repo:apache/arrow is:issue is:closed milestone:#{@release_version}", + type: ISSUE) { + issueCount + } + } + QUERY + } + response = Net::HTTP.post(uri, + n_issues_query.to_json, + "Content-Type" => "application/json", + "Authorization" => "Bearer #{github_token}") + n_resolved_issues = JSON.parse(response.body)["data"]["search"]["issueCount"] + github_repository = ENV["GITHUB_REPOSITORY"] || "apache/arrow" + github_api_url = "https://api.github.com" + verify_prs = URI("#{github_api_url}/repos/#{github_repository}/pulls" + + "?state=open" + + "&head=apache:release-#{@release_version}-rc0") + verify_pr_url = nil + headers = { + "Accept" => "application/vnd.github+json", + } + + if github_token + headers["Authorization"] = "Bearer #{github_token}" + end + verify_prs.open(headers) do |response| + verify_pr_url = (JSON.parse(response.read)[0] || {})["html_url"] + end + output = vote_email("TEMPLATE") + tarball_hash = Digest::SHA512.file("artifacts/#{@archive_name}").to_s + assert_equal(<<-TEMPLATE.strip, output[/^-+$(.+?)^-+$/m, 1].strip) +To: dev@arrow.apache.org +Subject: [VOTE] Release Apache Arrow #{@release_version} - RC0 + +Hi, + +I would like to propose the following release candidate (RC0) of Apache +Arrow version #{@release_version}. This is a release consisting of #{n_resolved_issues} +resolved GitHub issues[1]. + +This release candidate is based on commit: +#{@current_commit} [2] + +The source release rc0 is hosted at [3]. +The binary artifacts are hosted at [4][5][6][7][8][9]. +The changelog is located at [10]. + +Please download, verify checksums and signatures, run the unit tests, +and vote on the release. See [11] for the SHA-512 checksum for this RC and [12] +for how to validate a release candidate. + +See also a verification result on GitHub pull request [13]. + +The vote will be open for at least 72 hours. + +[ ] +1 Release this as Apache Arrow #{@release_version} +[ ] +0 +[ ] -1 Do not release this as Apache Arrow #{@release_version} because... + +[1]: https://github.com/apache/arrow/issues?q=is%3Aissue+milestone%3A#{@release_version}+is%3Aclosed +[2]: https://github.com/apache/arrow/tree/#{@current_commit} +[3]: https://dist.apache.org/repos/dist/dev/arrow/apache-arrow-#{@release_version}-rc0 +[4]: https://packages.apache.org/artifactory/arrow/almalinux-rc/ +[5]: https://packages.apache.org/artifactory/arrow/amazon-linux-rc/ +[6]: https://packages.apache.org/artifactory/arrow/centos-rc/ +[7]: https://packages.apache.org/artifactory/arrow/debian-rc/ +[8]: https://packages.apache.org/artifactory/arrow/ubuntu-rc/ +[9]: https://github.com/apache/arrow/releases/tag/apache-arrow-#{@release_version}-rc0 +[10]: https://github.com/apache/arrow/blob/#{@current_commit}/CHANGELOG.md +[11]: #{tarball_hash} +[12]: https://arrow.apache.org/docs/developers/release_verification.html +[13]: #{verify_pr_url || "null"} + TEMPLATE + end +end diff --git a/dev/release/09-vote-email.sh b/dev/release/09-vote-email.sh index e232a5845141..b5aebc1740b7 100755 --- a/dev/release/09-vote-email.sh +++ b/dev/release/09-vote-email.sh @@ -20,10 +20,10 @@ set -eu -: ${SOURCE_DEFAULT:=1} -: ${SOURCE_CLEANUP:=${SOURCE_DEFAULT}} -: ${SOURCE_DOWNLOAD:=${SOURCE_DEFAULT}} -: ${SOURCE_VOTE:=${SOURCE_DEFAULT}} +: ${VOTE_DEFAULT:=1} +: ${VOTE_CLEANUP:=${VOTE_DEFAULT}} +: ${VOTE_DOWNLOAD:=${VOTE_DEFAULT}} +: ${VOTE_TEMPLATE:=${VOTE_DEFAULT}} SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_TOP_DIR="$(cd "${SOURCE_DIR}/../../" && pwd)" @@ -47,21 +47,18 @@ rc_url="https://dist.apache.org/repos/dist/dev/arrow/${tag}" echo "Using commit $release_hash" -tarball=apache-arrow-${version}.tar.gz +tarball_sha512=apache-arrow-${version}.tar.gz.sha512 -if [ ${SOURCE_DOWNLOAD} -gt 0 ]; then - echo "Downloading tarball and checksums for ${tag}" - # Wait for the release candidate workflow to finish before attempting - # to download the tarball from the GitHub Release. - . $SOURCE_DIR/utils-watch-gh-workflow.sh ${tag} "release_candidate.yml" +if [ ${VOTE_DOWNLOAD} -gt 0 ]; then + echo "Downloading tarball checksum for ${tag}" rm -rf artifacts gh release download ${tag} \ --dir artifacts \ --repo "${GITHUB_REPOSITORY}" \ - --pattern "${tarball}.*" + --pattern "${tarball_sha512}" fi -if [ ${SOURCE_VOTE} -gt 0 ]; then +if [ ${VOTE_TEMPLATE} -gt 0 ]; then curl_common_options=(--header "Authorization: Bearer ${GH_TOKEN}") curl_options=("${curl_common_options[@]}") @@ -77,7 +74,7 @@ if [ ${SOURCE_VOTE} -gt 0 ]; then curl_options+=(https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls) verify_pr_url=$(curl "${curl_options[@]}" | jq -r ".[0].html_url") # Read the checksum so we can include it in the vote thread email. - sha512_path="artifacts/${tarball}.sha512" + sha512_path="artifacts/${tarball_sha512}" [[ -f "${sha512_path}" ]] || { echo "Error: ${sha512_path} must exist"; exit 1; } tarball_hash=$(cat "${sha512_path}" | awk '{print $1}') @@ -131,6 +128,6 @@ MAIL echo "---------------------------------------------------------" fi -if [ ${SOURCE_CLEANUP} -gt 0 ]; then +if [ ${VOTE_CLEANUP} -gt 0 ]; then rm -rf artifacts fi