diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0544ff11bf8..2b7e3ce7633 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -349,7 +349,8 @@ repos: ?^cpp/examples/tutorial_examples/run\.sh$| ?^cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc\.sh$| ?^dev/release/05-binary-upload\.sh$| - ?^dev/release/08-binary-verify\.sh$| + ?^dev/release/07-flightsqlodbc-upload\.sh$| + ?^dev/release/09-binary-verify\.sh$| ?^dev/release/binary-recover\.sh$| ?^dev/release/post-03-binary\.sh$| ?^dev/release/post-08-docs\.sh$| @@ -376,6 +377,7 @@ repos: ?^ci/scripts/python_test_type_annotations\.sh$| ?^cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc\.sh$| ?^dev/release/05-binary-upload\.sh$| + ?^dev/release/07-flightsqlodbc-upload\.sh$| ?^dev/release/binary-recover\.sh$| ?^dev/release/post-03-binary\.sh$| ?^dev/release/post-08-docs\.sh$| diff --git a/dev/release/.env.example b/dev/release/.env.example index 67bc944956c..3d522696294 100644 --- a/dev/release/.env.example +++ b/dev/release/.env.example @@ -34,3 +34,10 @@ # # You must set this. #GH_TOKEN=secret + +# For 07-flightsqlodbc-upload.sh. See that script for more details. +# +# ssl.com credentials in "username|password" format +#ESIGNER_STOREPASS=username|password +# ssl.com eSigner secret code (not the PIN) +#ESIGNER_KEYPASS=otp_secret_code diff --git a/dev/release/07-flightsqlodbc-upload.sh b/dev/release/07-flightsqlodbc-upload.sh new file mode 100755 index 00000000000..c47b5076f9c --- /dev/null +++ b/dev/release/07-flightsqlodbc-upload.sh @@ -0,0 +1,182 @@ +#!/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. +# +# FlightSQL ODBC Release Signing Script +# +# This script handles the signing of FlightSQL ODBC Windows binaries and MSI +# installer. It requires jsign to be configured with ASF code signing +# credentials. Keep reading below: +# +# Required environment variables: +# +# ESIGNER_STOREPASS - The ssl.com credentials in "username|password" format +# ESIGNER_KEYPASS - The ssl.com eSigner secret code (not the PIN) +# +# Set these in .env. +# +# How to get ESIGNER_KEYPASS: +# +# 1. Log into ssl.com +# 2. In your Dashboard, under "invitations", click the link under the order. Or +# go to Orders, find the order, expand the order, and click "certificate +# details" +# 3. Enter your PIN to get your OTP. This is ESIGNER_KEYPASS. +# +# If you don't have access, see https://infra.apache.org/code-signing-use.html. + +set -e +set -u +set -o pipefail + +SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +. "${SOURCE_DIR}/utils-env.sh" + +if [ -z "${ESIGNER_STOREPASS:-}" ]; then + echo "ERROR: ESIGNER_STOREPASS is not set" >&2 + exit 1 +fi +if [ -z "${ESIGNER_KEYPASS:-}" ]; then + echo "ERROR: ESIGNER_KEYPASS is not set" >&2 + exit 1 +fi + +version=$1 +rc=$2 + +version_with_rc="${version}-rc${rc}" +tag="apache-arrow-${version_with_rc}" + +dll_unsigned="arrow_flight_sql_odbc_unsigned.dll" +dll_signed="arrow_flight_sql_odbc.dll" + +: "${GITHUB_REPOSITORY:=apache/arrow}" + +: "${PHASE_DEFAULT:=1}" +: "${PHASE_SIGN_DLL:=${PHASE_DEFAULT}}" +: "${PHASE_BUILD_MSI:=${PHASE_DEFAULT}}" +: "${PHASE_SIGN_MSI:=${PHASE_DEFAULT}}" + +if [ "${PHASE_SIGN_DLL}" -eq 0 ] && [ "${PHASE_BUILD_MSI}" -eq 0 ] && [ "${PHASE_SIGN_MSI}" -eq 0 ]; then + echo "No phases specified. Exiting." + exit 1 +fi + +# Utility function to use jsign to check if a file is signed or not +is_signed() { + local file="$1" + local exit_code=0 + jsign extract --format PEM "${file}" >/dev/null 2>&1 || exit_code=$? + # jsign writes a PEM file even though it also prints to stdout. Clean up after + # it. Use -f since so it still runs on unsigned files without error. + rm -f "${file}.sig.pem" + return ${exit_code} +} + +# Use dev/release/tmp for temporary files +tmp_dir="${SOURCE_DIR}/tmp" +if [ -e "${tmp_dir}" ]; then + echo "ERROR: temp dir already exists: ${tmp_dir}. Remove it manually and run again." >&2 + exit 1 +fi + +if [ "${PHASE_SIGN_DLL}" -gt 0 ]; then + echo "[1/8 Downloading ${dll_unsigned} from release..." + gh release download "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --pattern "${dll_unsigned}" \ + --dir "${tmp_dir}" + if is_signed "${tmp_dir}/${dll_unsigned}"; then + echo "ERROR: ${dll_unsigned} is already signed" >&2 + exit 1 + fi + + echo "[2/8 Signing ${dll_unsigned}..." + echo "NOTE: Running jsign. You may be prompted for your OTP PIN..." + jsign --storetype ESIGNER \ + --alias d97c5110-c66a-4c0c-ac0c-1cd6af812ee6 \ + --storepass "${ESIGNER_STOREPASS}" \ + --keypass "${ESIGNER_KEYPASS}" \ + --tsaurl="http://ts.ssl.com" \ + --tsmode RFC3161 \ + --alg SHA256 \ + "${tmp_dir}/${dll_unsigned}" + mv "${tmp_dir}/${dll_unsigned}" "${tmp_dir}/${dll_signed}" + if ! is_signed "${tmp_dir}/${dll_signed}"; then + echo "ERROR: ${dll_signed} is not signed" >&2 + exit 1 + fi + + echo "[3/8 Uploading signed DLL to GitHub Release..." + gh release upload "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --clobber \ + "${tmp_dir}/${dll_signed}" +fi + +if [ "${PHASE_BUILD_MSI}" -gt 0 ]; then + echo "[4/8 Triggering odbc_release_step in cpp_extra.yml workflow..." + gh workflow run cpp_extra.yml \ + --repo "${GITHUB_REPOSITORY}" \ + --ref "${tag}" \ + --field odbc_release_step=true + + echo "[5/8 Waiting for workflow to complete. This can take a very long time..." + REPOSITORY="${GITHUB_REPOSITORY}" \ + "${SOURCE_DIR}/utils-watch-gh-workflow.sh" "${tag}" cpp_extra.yml +fi + +if [ "${PHASE_SIGN_MSI}" -gt 0 ]; then + echo "[6/8 Downloading unsigned MSI..." + gh release download "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --pattern "Apache-Arrow-Flight-SQL-ODBC-${version}-win64.msi" \ + --dir "${tmp_dir}" + msi="${tmp_dir}/Apache-Arrow-Flight-SQL-ODBC-${version}-win64.msi" + if is_signed "${msi}"; then + echo "ERROR: MSI is already signed" >&2 + exit 1 + fi + + echo "[7/8 Signing MSI..." + echo "NOTE: Running jsign. You may be prompted for your OTP PIN..." + jsign --storetype ESIGNER \ + --alias d97c5110-c66a-4c0c-ac0c-1cd6af812ee6 \ + --storepass "${ESIGNER_STOREPASS}" \ + --keypass "${ESIGNER_KEYPASS}" \ + --tsaurl="http://ts.ssl.com" \ + --tsmode RFC3161 \ + --alg SHA256 \ + "${msi}" + if ! is_signed "${msi}"; then + echo "ERROR: MSI is not signed" >&2 + exit 1 + fi + + echo "[8/8 Uploading signed MSI to GitHub Release..." + gh release upload "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --clobber \ + "${msi}" +fi diff --git a/dev/release/07-publish-gh-release.sh b/dev/release/08-publish-gh-release.sh similarity index 100% rename from dev/release/07-publish-gh-release.sh rename to dev/release/08-publish-gh-release.sh diff --git a/dev/release/08-binary-verify.sh b/dev/release/09-binary-verify.sh similarity index 100% rename from dev/release/08-binary-verify.sh rename to dev/release/09-binary-verify.sh diff --git a/dev/release/09-vote-email-test.rb b/dev/release/10-vote-email-test.rb similarity index 98% rename from dev/release/09-vote-email-test.rb rename to dev/release/10-vote-email-test.rb index bae314b2451..2423856c0b2 100644 --- a/dev/release/09-vote-email-test.rb +++ b/dev/release/10-vote-email-test.rb @@ -24,7 +24,7 @@ def setup 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") + @script = File.expand_path("dev/release/10-vote-email.sh") @tarball_script = File.expand_path("dev/release/utils-create-release-tarball.sh") @env = File.expand_path("dev/release/.env") diff --git a/dev/release/09-vote-email.sh b/dev/release/10-vote-email.sh similarity index 100% rename from dev/release/09-vote-email.sh rename to dev/release/10-vote-email.sh diff --git a/docs/source/developers/release.rst b/docs/source/developers/release.rst index 631019f4468..fe5a969737f 100644 --- a/docs/source/developers/release.rst +++ b/docs/source/developers/release.rst @@ -264,13 +264,24 @@ Build source and binaries and submit them # NOTE: You need to have GitHub CLI installed to run this script. dev/release/06-matlab-upload.sh + # Sign, build the installer for, and sign the installer for the FlightSQL + # ODBC Windows driver + # + # NOTE: This must be run by a PMC member + # Note: You need to have jsign installed and an available credential from + # ASF to sign artifacts. Not all PMC members will have access to code + # signing. + # Note: The script requires setup of ssl.com environment variables. + # Note: Invoking this script costs money. + dev/release/07-flightsqlodbc-upload.sh + # Move the Release Candidate GitHub Release from draft to published state # This will update the artifacts download URL which will be available for the # verification step. - dev/release/07-publish-gh-release.sh + dev/release/08-publish-gh-release.sh # Start verifications for binaries and wheels - dev/release/08-binary-verify.sh + dev/release/09-binary-verify.sh Verify the Release @@ -280,7 +291,7 @@ Verify the Release # Once the automatic verification has passed start the vote thread # on dev@arrow.apache.org. To regenerate the email template use - dev/release/09-vote-email.sh + dev/release/10-vote-email.sh See :ref:`release_verification` for details.