This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/terraform-provider-paimon.git
The following commit(s) were added to refs/heads/main by this push:
new cd0e9fb Harden release tooling and expand the release guide (#34)
cd0e9fb is described below
commit cd0e9fb67c77bab7b0019310b97c49f4380a5194
Author: Jiajia Li <[email protected]>
AuthorDate: Sat Oct 10 16:31:32 2026 +0800
Harden release tooling and expand the release guide (#34)
---
.github/workflows/tf-release.yml | 2 +-
.gitignore | 2 +-
dev/release/release.sh | 111 ++++++++++++++++++--
dev/release/release_rc.sh | 119 +++++++++++++++++++---
dev/release/verify_rc.sh | 116 +++++++++++++++++----
docs/releasing.md | 211 ++++++++++++++++++++++++++++++++-------
6 files changed, 477 insertions(+), 84 deletions(-)
diff --git a/.github/workflows/tf-release.yml b/.github/workflows/tf-release.yml
index f00f218..432ee8c 100644
--- a/.github/workflows/tf-release.yml
+++ b/.github/workflows/tf-release.yml
@@ -48,7 +48,7 @@ jobs:
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
- directory="apache-paimon-terraform-${VERSION}"
+ directory="paimon-terraform-${VERSION}"
archive="apache-paimon-terraform-${VERSION}-src.tgz"
git archive --format=tar.gz --prefix="${directory}/" -o "${archive}"
HEAD
sha512sum "${archive}" > "${archive}.sha512"
diff --git a/.gitignore b/.gitignore
index b3123d0..a541ce0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,7 @@ terraform-plugins/
build/
lib/
dist/
-apache-paimon-terraform-*.tar.gz
+apache-paimon-terraform-*-src.tgz*
# Editors and operating systems
.idea/
diff --git a/dev/release/release.sh b/dev/release/release.sh
index 1f562e8..1a2200a 100755
--- a/dev/release/release.sh
+++ b/dev/release/release.sh
@@ -14,6 +14,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Finalizes a release after the vote passes. It promotes the exact artifacts
+# that were voted on; nothing is rebuilt:
+#
+# 1. Creates a signed v<version> tag at the RC commit and pushes it.
+# 2. Moves the source release from dist/dev to dist/release.
+# 3. Creates the final GitHub Release with the promoted source release and
the
+# signed convenience binaries from the RC prerelease. The Terraform and
+# OpenTofu registries ingest this release.
+#
+# <rc> is the release candidate that passed the vote. RELEASE_REMOTE names the
+# git remote for apache/terraform-provider-paimon (default: origin), and
+# GPG_LOCAL_USER selects the tag signing key when several are available.
+
set -euo pipefail
if [[ $# -ne 2 || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! "$2" =~
^[1-9][0-9]*$ ]]; then
@@ -26,28 +39,110 @@ tag="v${version}"
rc_tag="${tag}-rc${rc}"
repository="apache/terraform-provider-paimon"
release_id="paimon-terraform-${version}"
+binary_sums="terraform-provider-paimon_${version}_SHA256SUMS"
dist_dev="https://dist.apache.org/repos/dist/dev/paimon"
dist_release="https://dist.apache.org/repos/dist/release/paimon"
+: "${RELEASE_REMOTE:=origin}"
+
+tag_args=(-s)
+if [[ -n "${GPG_LOCAL_USER:-}" ]]; then
+ tag_args=(-u "${GPG_LOCAL_USER}")
+fi
+
+repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+cd "${repo_root}"
+
+# Check every fetch and push URL, since a remote can push somewhere other than
+# where it fetches from. URLs are never printed: they may embed credentials.
+remote_urls="$(git remote get-url --all "${RELEASE_REMOTE}")"
+remote_urls+=$'\n'"$(git remote get-url --push --all "${RELEASE_REMOTE}")"
+while IFS= read -r remote_url; do
+ case "${remote_url}" in
+ "[email protected]:${repository}.git" | "https://github.com/${repository}" |
"https://github.com/${repository}.git") ;;
+ *)
+ echo "remote ${RELEASE_REMOTE} has a fetch or push URL that is not
${repository}" >&2
+ echo "set RELEASE_REMOTE to a remote whose URLs all point at the Apache
repository" >&2
+ exit 1
+ ;;
+ esac
+done <<<"${remote_urls}"
if [[ -n "$(git status --porcelain)" ]]; then
echo "final releases must be published from a clean checkout" >&2
exit 1
fi
+git fetch "${RELEASE_REMOTE}" "refs/tags/${rc_tag}:refs/tags/${rc_tag}"
rc_commit="$(git rev-parse "${rc_tag}^{commit}")"
-git tag -a "${tag}" "${rc_commit}" -m "Apache Paimon Terraform provider
${version}"
-git push origin "${tag}"
-
-svn mv "${dist_dev}/${release_id}-rc${rc}" "${dist_release}/${release_id}" \
- -m "Apache Paimon Terraform provider ${version}"
work_dir="$(mktemp -d -t paimon-provider-release.XXXXXX)"
trap 'rm -rf "${work_dir}"' EXIT
-svn export "${dist_release}/${release_id}" "${work_dir}/source"
+
+# The registries reject a release without the signed checksums, so check the
+# voted assets before anything public changes.
gh release download "${rc_tag}" --repo "${repository}" --dir
"${work_dir}/assets" \
--pattern 'terraform-provider-paimon_*'
+for required in "${binary_sums}" "${binary_sums}.sig"
"terraform-provider-paimon_${version}_manifest.json"; do
+ [[ -f "${work_dir}/assets/${required}" ]] || { echo "RC prerelease is
missing ${required}" >&2; exit 1; }
+done
+gpg --verify "${work_dir}/assets/${binary_sums}.sig"
"${work_dir}/assets/${binary_sums}"
+
+git tag "${tag_args[@]}" "${tag}" "${rc_commit}" -m "Apache Paimon Terraform
${version}"
+git tag -v "${tag}"
+git push "${RELEASE_REMOTE}" "${tag}"
+
+svn mv "${dist_dev}/${release_id}-rc${rc}" "${dist_release}/${release_id}" \
+ -m "Apache Paimon Terraform provider ${version}"
+
+svn export "${dist_release}/${release_id}" "${work_dir}/source"
cp "${work_dir}/source/"* "${work_dir}/assets/"
gh release create "${tag}" --repo "${repository}" --title "Apache Paimon
Terraform provider ${version}" \
--generate-notes --verify-tag "${work_dir}/assets/"*
-echo "Published ${tag}: https://github.com/${repository}/releases/tag/${tag}"
-echo "Remember to record the release at
https://reporter.apache.org/addrelease.html?paimon"
+echo "Published ${tag}:"
+echo " ${dist_release}/${release_id}"
+echo " https://github.com/${repository}/releases/tag/${tag}"
+echo
+echo "Record the release at https://reporter.apache.org/addrelease.html?paimon"
+
+# ASF policy keeps the latest release of every maintained line in dist/release;
+# older ones remain on archive.apache.org. Only older patch releases of this
+# release line are superseded. Retiring another line is a separate decision.
+release_line="${version%.*}"
+superseded=()
+while IFS= read -r old; do
+ old_version="${old#paimon-terraform-}"
+ if [[ "${old_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && "${old_version%.*}" ==
"${release_line}" &&
+ ${old_version##*.} -lt ${version##*.} ]]; then
+ superseded+=("${old}")
+ fi
+done < <(svn ls "${dist_release}" | sed 's|/$||')
+if [[ ${#superseded[@]} -gt 0 ]]; then
+ echo
+ echo "Older ${release_line}.x releases superseded by ${version} in
dist/release:"
+ for old in "${superseded[@]}"; do
+ echo " svn delete -m 'Remove superseded release ${old}'
${dist_release}/${old}"
+ done
+fi
+
+cat <<MAIL
+
+Wait at least 24 hours after publishing and until both registries list
+${version} (check with dev/release/verify_registry.sh). Then send this as plain
+text from your @apache.org address:
+
+To: [email protected], [email protected]
+Subject: [ANNOUNCE] Release Apache Paimon Terraform ${version}
+
+The Apache Paimon community is pleased to announce the release of Apache
+Paimon Terraform ${version}.
+
+The provider manages Apache Paimon REST Catalog databases, tables, and access
+policies with Terraform and OpenTofu.
+
+Source release: https://downloads.apache.org/paimon/${release_id}/
+Release notes: https://github.com/${repository}/releases/tag/${tag}
+Terraform:
https://registry.terraform.io/providers/apache/paimon/${version}
+OpenTofu: https://search.opentofu.org/provider/apache/paimon/v${version}
+
+Thanks to everyone who contributed to this release.
+MAIL
diff --git a/dev/release/release_rc.sh b/dev/release/release_rc.sh
index 125891c..8302129 100755
--- a/dev/release/release_rc.sh
+++ b/dev/release/release_rc.sh
@@ -14,6 +14,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Cuts a release candidate for the Apache Paimon Terraform provider:
+#
+# 1. Runs the release checks, then creates a signed v<version>-rc<rc> tag and
+# pushes it, which triggers the "Terraform Build Release Candidate"
+# workflow (.github/workflows/tf-release.yml). CI builds the source
archive and the
+# convenience binaries and publishes them as a GitHub prerelease.
+# 2. Waits for that workflow, then signs the source archive (.asc) and the
+# binary SHA256SUMS (.sig) with your KEYS-registered GPG key and uploads
the
+# signatures back to the prerelease.
+# 3. Uploads the signed source release, the artifact the PMC votes on, to
+# dist/dev. Convenience binaries stay on the GitHub prerelease.
+# 4. Prints the [VOTE] email for [email protected].
+#
+# Set RELEASE_PULL=0, RELEASE_PUSH_TAG=0, RELEASE_SIGN=0, or RELEASE_UPLOAD=0
+# (or RELEASE_DEFAULT=0) to skip individual stages. RELEASE_REMOTE names the
git
+# remote for apache/terraform-provider-paimon (default: origin), and
+# GPG_LOCAL_USER selects the signing key when several are available.
+
set -euo pipefail
if [[ $# -ne 2 || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! "$2" =~
^[1-9][0-9]*$ ]]; then
@@ -29,28 +47,72 @@
binary_sums="terraform-provider-paimon_${version}_SHA256SUMS"
staging_name="paimon-terraform-${version}-rc${rc}"
: "${RELEASE_DEFAULT:=1}"
+: "${RELEASE_PULL:=${RELEASE_DEFAULT}}"
: "${RELEASE_PUSH_TAG:=${RELEASE_DEFAULT}}"
: "${RELEASE_SIGN:=${RELEASE_DEFAULT}}"
: "${RELEASE_UPLOAD:=${RELEASE_DEFAULT}}"
+: "${RELEASE_REMOTE:=origin}"
+
+gpg_args=()
+tag_args=(-s)
+if [[ -n "${GPG_LOCAL_USER:-}" ]]; then
+ gpg_args=(--local-user "${GPG_LOCAL_USER}")
+ tag_args=(-u "${GPG_LOCAL_USER}")
+fi
+
+sha512_check() {
+ if command -v sha512sum >/dev/null; then sha512sum -c "$@"; else shasum -a
512 -c "$@"; fi
+}
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${repo_root}"
+
+# Check every fetch and push URL, since a remote can push somewhere other than
+# where it fetches from. URLs are never printed: they may embed credentials.
+remote_urls="$(git remote get-url --all "${RELEASE_REMOTE}")"
+remote_urls+=$'\n'"$(git remote get-url --push --all "${RELEASE_REMOTE}")"
+while IFS= read -r remote_url; do
+ case "${remote_url}" in
+ "[email protected]:${repository}.git" | "https://github.com/${repository}" |
"https://github.com/${repository}.git") ;;
+ *)
+ echo "remote ${RELEASE_REMOTE} has a fetch or push URL that is not
${repository}" >&2
+ echo "set RELEASE_REMOTE to a remote whose URLs all point at the Apache
repository" >&2
+ exit 1
+ ;;
+ esac
+done <<<"${remote_urls}"
+
if [[ -n "$(git status --porcelain)" ]]; then
echo "release candidates must be cut from a clean checkout" >&2
exit 1
fi
-./dev/update_licenses.sh --check
-make check
-make check-license
-make test-acceptance
+if [[ ${RELEASE_PULL} -gt 0 ]]; then
+ # main for a major/minor release, paimon-terraform-X.Y.x for a patch release.
+ branch="$(git symbolic-ref --short HEAD)"
+ echo "Updating ${branch} from ${RELEASE_REMOTE}"
+ git pull --ff-only "${RELEASE_REMOTE}" "${branch}"
+fi
+
+if [[ ${RELEASE_PUSH_TAG} -gt 0 ]]; then
+ if git rev-parse --quiet --verify "refs/tags/${tag}" >/dev/null ||
+ [[ -n "$(git ls-remote --tags "${RELEASE_REMOTE}" "refs/tags/${tag}")" ]];
then
+ echo "tag ${tag} already exists; use the next RC number" >&2
+ exit 1
+ fi
+ ./dev/update_licenses.sh --check
+ make check
+ make check-license
+ make test-acceptance
+fi
work_dir="$(mktemp -d -t paimon-provider-rc.XXXXXX)"
trap 'rm -rf "${work_dir}"' EXIT
staging="${work_dir}/stage"
if [[ ${RELEASE_PUSH_TAG} -gt 0 ]]; then
- git tag -a "${tag}" -m "Apache Paimon Terraform provider ${version} RC${rc}"
- git push origin "${tag}"
+ git tag "${tag_args[@]}" "${tag}" -m "Apache Paimon Terraform ${version}
RC${rc}"
+ git tag -v "${tag}"
+ git push "${RELEASE_REMOTE}" "${tag}"
fi
rc_hash="$(git rev-list --max-count=1 "${tag}")"
@@ -61,7 +123,7 @@ if [[ ${RELEASE_SIGN} -gt 0 ]]; then
[[ -n "${run_id}" ]] && break
sleep 5
done
- [[ -n "${run_id}" ]] || { echo "timed out waiting for tf-release.yml" >&2;
exit 1; }
+ [[ -n "${run_id}" ]] || { echo "timed out waiting for tf-release.yml on
${tag}" >&2; exit 1; }
gh run watch --repo "${repository}" --exit-status "${run_id}"
mkdir "${staging}"
@@ -69,15 +131,18 @@ if [[ ${RELEASE_SIGN} -gt 0 ]]; then
--pattern "${source_archive}" --pattern "${source_archive}.sha512"
--pattern "${binary_sums}"
(
cd "${staging}"
- gpg --armor --detach-sign "${source_archive}"
- gpg --batch --yes --detach-sign --output "${binary_sums}.sig"
"${binary_sums}"
+ sha512_check "${source_archive}.sha512"
+ gpg ${gpg_args[@]+"${gpg_args[@]}"} --armor --detach-sign
"${source_archive}"
+ gpg ${gpg_args[@]+"${gpg_args[@]}"} --batch --yes --detach-sign --output
"${binary_sums}.sig" "${binary_sums}"
+ gpg --verify "${source_archive}.asc" "${source_archive}"
+ gpg --verify "${binary_sums}.sig" "${binary_sums}"
)
gh release upload "${tag}" --repo "${repository}" --clobber \
"${staging}/${source_archive}.asc" "${staging}/${binary_sums}.sig"
fi
if [[ ${RELEASE_UPLOAD} -gt 0 ]]; then
- [[ -d "${staging}" ]] || { echo "missing signed staging directory
${staging}" >&2; exit 1; }
+ [[ -d "${staging}" ]] || { echo "RELEASE_UPLOAD requires RELEASE_SIGN in the
same run" >&2; exit 1; }
rm -f "${staging}/${binary_sums}" "${staging}/${binary_sums}.sig"
svn import "${staging}"
"https://dist.apache.org/repos/dist/dev/paimon/${staging_name}" \
-m "Apache Paimon Terraform provider ${version} RC${rc}"
@@ -85,11 +150,35 @@ fi
cat <<MAIL
To: [email protected]
-Subject: [VOTE][Terraform] Release Apache Paimon Terraform provider
v${version} RC${rc}
+Subject: [VOTE] Release Apache Paimon Terraform ${version} (RC${rc})
+
+Hi everyone,
+
+Please review and vote on release candidate ${rc} of Apache Paimon
+Terraform ${version}, the Terraform and OpenTofu provider for Apache Paimon.
+
+The candidate is based on commit ${rc_hash} [1].
+
+The source release, its signature, and its SHA-512 checksum are staged at [2].
+The release is signed with a key from the Apache Paimon KEYS file [3].
+
+Convenience binaries for the Terraform and OpenTofu registries, with their
+signed SHA256SUMS, are attached to the GitHub prerelease [4]. Only the source
+release is voted on.
+
+See [5] for how to verify the release candidate:
+
+ ./dev/release/verify_rc.sh ${version} ${rc}
+
+The vote will be open for at least 72 hours.
-The candidate is based on ${rc_hash}.
-Source: https://dist.apache.org/repos/dist/dev/paimon/${staging_name}
-Convenience binaries: https://github.com/${repository}/releases/tag/${tag}
+[ ] +1 Release this as Apache Paimon Terraform ${version}
+[ ] +0
+[ ] -1 Do not release this as Apache Paimon Terraform ${version} because...
-The vote is open for at least 72 hours.
+[1] https://github.com/${repository}/tree/${rc_hash}
+[2] https://dist.apache.org/repos/dist/dev/paimon/${staging_name}/
+[3] https://downloads.apache.org/paimon/KEYS
+[4] https://github.com/${repository}/releases/tag/${tag}
+[5]
https://github.com/${repository}/blob/main/docs/releasing.md#verifying-a-release-candidate
MAIL
diff --git a/dev/release/verify_rc.sh b/dev/release/verify_rc.sh
index 1ad1c09..690cd94 100755
--- a/dev/release/verify_rc.sh
+++ b/dev/release/verify_rc.sh
@@ -14,6 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Verifies a release candidate of the Apache Paimon Terraform provider so you
+# can cast an informed vote. It checks the source release signature and SHA-512
+# checksum, the convenience binaries against their signed SHA256SUMS, runs the
+# Apache RAT license check, and builds and tests the provider from source in a
+# throwaway sandbox.
+#
+# Requirements: bash, curl, gpg, Go, Terraform, sha512sum/sha256sum (or
+# shasum), gh for the binaries, and java plus unzip for RAT.
+#
+# Set VERIFY_SIGN=0, VERIFY_BINARY=0, VERIFY_RAT=0, or VERIFY_ACCEPTANCE=0 to
+# skip a stage, and VERIFY_ARTIFACTS_DIR=<dir> to verify a local copy of the
+# source release instead of downloading it from dist/dev. A failed run keeps
its
+# sandbox for inspection.
+
set -euo pipefail
if [[ $# -ne 2 || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! "$2" =~
^[1-9][0-9]*$ ]]; then
@@ -22,37 +36,97 @@ if [[ $# -ne 2 || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || !
"$2" =~ ^[1-9][0-9]*$
fi
version="$1"
rc="$2"
-release_id="paimon-terraform-${version}"
-rc_id="${release_id}-rc${rc}"
+rc_id="paimon-terraform-${version}-rc${rc}"
source_archive="apache-paimon-terraform-${version}-src.tgz"
-source_directory="apache-paimon-terraform-${version}"
+source_directory="paimon-terraform-${version}"
repository="apache/terraform-provider-paimon"
binary_sums="terraform-provider-paimon_${version}_SHA256SUMS"
+: "${VERIFY_DEFAULT:=1}"
+: "${VERIFY_SIGN:=${VERIFY_DEFAULT}}"
+: "${VERIFY_BINARY:=${VERIFY_DEFAULT}}"
+: "${VERIFY_RAT:=${VERIFY_DEFAULT}}"
+: "${VERIFY_ACCEPTANCE:=${VERIFY_DEFAULT}}"
+: "${VERIFY_ARTIFACTS_DIR:=}"
+if [[ -n "${VERIFY_ARTIFACTS_DIR}" ]]; then
+ VERIFY_ARTIFACTS_DIR="$(cd "${VERIFY_ARTIFACTS_DIR}" && pwd)"
+fi
+
+sha_check() {
+ local bits="$1"
+ shift
+ if command -v "sha${bits}sum" >/dev/null; then
+ "sha${bits}sum" -c "$@"
+ else
+ shasum -a "${bits}" -c "$@"
+ fi
+}
+
work_dir="$(mktemp -d -t paimon-provider-verify.XXXXXX)"
-trap 'rm -rf "${work_dir}"' EXIT
+verified=0
+cleanup() {
+ if [[ ${verified} -eq 1 ]]; then
+ chmod -R u+w "${work_dir}" 2>/dev/null || true
+ rm -rf "${work_dir}"
+ else
+ echo "Release candidate verification failed; see ${work_dir}" >&2
+ fi
+}
+trap cleanup EXIT
cd "${work_dir}"
-curl -fsSLO https://downloads.apache.org/paimon/KEYS
-gpg --import KEYS
-for suffix in "" .asc .sha512; do
- curl -fsSLO
"https://dist.apache.org/repos/dist/dev/paimon/${rc_id}/${source_archive}${suffix}"
+echo "Working in ${work_dir}"
+
+# Keep downloaded modules and build output out of the voter's own Go caches.
+export GOPATH="${work_dir}/gopath"
+export GOMODCACHE="${GOPATH}/pkg/mod"
+export GOCACHE="${work_dir}/gocache"
+export GOFLAGS="-modcacherw"
+
+if [[ ${VERIFY_SIGN} -gt 0 ]]; then
+ export GNUPGHOME="${work_dir}/gnupg"
+ mkdir -m 700 "${GNUPGHOME}"
+ curl -fsSLO https://downloads.apache.org/paimon/KEYS
+ gpg --batch --import KEYS
+fi
+
+source_files=("${source_archive}" "${source_archive}.sha512")
+[[ ${VERIFY_SIGN} -gt 0 ]] && source_files+=("${source_archive}.asc")
+for file in "${source_files[@]}"; do
+ if [[ -n "${VERIFY_ARTIFACTS_DIR}" ]]; then
+ cp "${VERIFY_ARTIFACTS_DIR}/${file}" .
+ else
+ curl -fsSLO
"https://dist.apache.org/repos/dist/dev/paimon/${rc_id}/${file}"
+ fi
done
-gpg --verify "${source_archive}.asc" "${source_archive}"
-shasum -a 512 -c "${source_archive}.sha512"
-
-mkdir binaries
-gh release download "v${version}-rc${rc}" --repo "${repository}" --dir
binaries \
- --pattern 'terraform-provider-paimon_*'
-(
- cd binaries
- gpg --verify "${binary_sums}.sig" "${binary_sums}"
- shasum -a 256 -c "${binary_sums}"
-)
+if [[ ${VERIFY_SIGN} -gt 0 ]]; then
+ gpg --batch --verify "${source_archive}.asc" "${source_archive}"
+fi
+sha_check 512 "${source_archive}.sha512"
+
+if [[ ${VERIFY_BINARY} -gt 0 ]]; then
+ # Convenience binaries live on the GitHub prerelease, not in dist/dev.
+ mkdir binaries
+ gh release download "v${version}-rc${rc}" --repo "${repository}" --dir
binaries \
+ --pattern 'terraform-provider-paimon_*'
+ (
+ cd binaries
+ if [[ ${VERIFY_SIGN} -gt 0 ]]; then
+ gpg --batch --verify "${binary_sums}.sig" "${binary_sums}"
+ fi
+ sha_check 256 "${binary_sums}"
+ )
+fi
tar -xzf "${source_archive}"
cd "${source_directory}"
-dev/check-license
+if [[ ${VERIFY_RAT} -gt 0 ]]; then
+ dev/check-license
+fi
./dev/update_licenses.sh --check
make check
-make test-acceptance
+if [[ ${VERIFY_ACCEPTANCE} -gt 0 ]]; then
+ make test-acceptance
+fi
+
+verified=1
echo "Release candidate ${version} RC${rc} verified successfully."
diff --git a/docs/releasing.md b/docs/releasing.md
index 765985f..5fda01d 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -19,22 +19,59 @@
# Releasing the Paimon Terraform provider
-The voted Apache release artifact is the source tarball. The zip files attached
+This guide follows the [Apache release
policy](https://www.apache.org/legal/release-policy.html).
+
+The voted Apache release artifact is the source archive. The zip files attached
to GitHub Releases are convenience binaries consumed by the Terraform and
OpenTofu registries. CI builds candidates but never holds a release manager's
-private key.
+private key, and the final release promotes the voted files without rebuilding
+them.
+
+| Artifact | Location |
+| --- | --- |
+| Source release |
`dist/{dev,release}/paimon/paimon-terraform-<version>[-rc<n>]/apache-paimon-terraform-<version>-src.tgz`
with `.asc` and `.sha512` |
+| Convenience binaries | GitHub Release `v<version>[-rc<n>]`:
`terraform-provider-paimon_<version>_<os>_<arch>.zip` for linux, darwin,
windows, and freebsd on amd64 and arm64 |
+| Registry metadata | `terraform-provider-paimon_<version>_SHA256SUMS`,
`..._SHA256SUMS.sig`, and `..._manifest.json` on the same GitHub Release |
## Prerequisites
-- A GPG key published in the Apache Paimon `KEYS` file and registered with the
- Terraform Registry `apache` namespace.
+- A GPG key published in the [Apache Paimon `KEYS`
file](https://downloads.apache.org/paimon/KEYS).
+ See [Set up a GPG key](#set-up-a-gpg-key).
- Commit access to `dist.apache.org/repos/dist/dev/paimon` and PMC access for
- `dist.apache.org/repos/dist/release/paimon`.
-- `git`, `svn`, `gh`, `gpg`, Go, Terraform, and `shasum` on `PATH`.
+ `dist.apache.org/repos/dist/release/paimon`. Work with a PMC member if you do
+ not have PMC access.
+- Write access to `apache/terraform-provider-paimon`, and a git remote that
+ points at it. The scripts use `origin` unless `RELEASE_REMOTE` names another
+ remote, and refuse to run against a fork.
+- `git`, `svn`, `gh`, `gpg`, `curl`, Go, Terraform, `java`, `unzip`, and
+ `sha512sum`/`sha256sum` (or `shasum`) on `PATH`.
- OpenTofu and Python 3 for the public Registry installation check.
-- A clean checkout whose `origin` is the Apache repository.
-Before cutting a candidate, run:
+### Registry onboarding (first release only)
+
+The registries read the final GitHub Release of
+`apache/terraform-provider-paimon`. Both need one-time setup:
+
+- **Terraform Registry.** A member of the `apache` GitHub organization with
+ admin rights must publish the `apache/paimon` provider from the repository
and
+ register the release manager's public key under the namespace's
+ **Settings → GPG Keys**. Request this through an
+ [ASF Infra ticket](https://issues.apache.org/jira/projects/INFRA) before the
+ first vote ends. Every later release manager's key must be added the same
way.
+- **OpenTofu Registry.** After the first final GitHub Release exists, open a
+ *Provider* issue for `apache/paimon` and a *Provider Key* issue with the
+ release key on
[`opentofu/registry`](https://github.com/opentofu/registry/issues/new/choose).
+ Later versions are picked up automatically.
+
+## Prepare the release
+
+- Remove attributes, resources, and data sources whose deprecation period ends
+ in this version, and update `docs/` and `examples/` to match.
+- Confirm the provider address in [`main.go`](../main.go) is
+ `registry.terraform.io/apache/paimon`.
+- Refresh the binary license inventory and commit any change.
+ `LICENSE-binary`, `NOTICE-binary`, and `licenses-binary/` describe the code
+ statically linked into every convenience binary and must match what ships.
```bash
./dev/update_licenses.sh
@@ -49,32 +86,66 @@ Complete the [production validation
matrix](production-readiness.md) against
each supported real Catalog/DLF and query-engine deployment, recording exact
versions and job results before claiming production compatibility.
-Commit any refreshed `LICENSE-binary`, `NOTICE-binary`, or `licenses-binary/`
-files. They describe the code statically linked into every convenience binary.
+### Versioning
-## Release candidate
+The git tag carries the version; there is no version string to bump in source.
+GoReleaser injects it into `main.version`. Versions follow
+[semantic versioning](https://semver.org/) with a `v` prefix (`v0.1.0`), and
+release candidates add `-rc<n>` starting at 1 (`v0.1.0-rc1`).
-From the branch being released:
+Major and minor releases are cut from `main`. Patch releases are cut from a
+`paimon-terraform-X.Y.x` branch created from the previous release tag:
```bash
-./dev/release/release_rc.sh 0.1.0 1
+git fetch --tags <apache-remote>
+git checkout -b paimon-terraform-0.1.x v0.1.0
+git cherry-pick <commit>
+git push <apache-remote> paimon-terraform-0.1.x
```
-The script creates and pushes `v0.1.0-rc1`. The release workflow builds a
-SHA-512 source artifact and GoReleaser produces signed-ready Registry zip
files,
-the manifest, and `SHA256SUMS`. The release manager signs the source and binary
-checksums, stages the source under ASF `dist/dev`, and sends the printed vote
to
-`[email protected]`. The vote remains open for at least 72 hours and needs
-three binding `+1` votes.
+## Release candidate
-Reviewers verify the candidate with:
+From a clean checkout of the branch being released:
```bash
-./dev/release/verify_rc.sh 0.1.0 1
+./dev/release/release_rc.sh 0.1.0 1
```
-This validates the KEYS-backed GPG signatures, SHA-512/SHA-256 checksums,
-binary inventory, Apache RAT result, build, and tests.
+The script:
+
+1. Fast-forwards the branch from the Apache remote, refuses an existing tag,
+ runs the release checks, then creates a GPG-signed `v0.1.0-rc1` tag and
+ pushes it.
+2. Waits for the [release workflow](../.github/workflows/tf-release.yml), which
+ builds the source archive with its SHA-512 checksum, builds the Registry
+ zip files, `SHA256SUMS`, and manifest with GoReleaser, and publishes them as
+ a GitHub prerelease.
+3. Signs the source archive (`.asc`) and binary checksums (`SHA256SUMS.sig`)
+ locally and uploads both signatures to the prerelease.
+4. Stages the signed source release in ASF `dist/dev` and prints the vote
+ email.
+
+Set `RELEASE_PULL=0`, `RELEASE_PUSH_TAG=0`, `RELEASE_SIGN=0`, or
+`RELEASE_UPLOAD=0` (or `RELEASE_DEFAULT=0`) to skip a stage, for example to
+re-sign an existing candidate without re-tagging. Set `GPG_LOCAL_USER` to
+choose the key for the tag and artifact signatures.
+
+## Vote
+
+Add a short summary of notable changes to the printed
+`[VOTE] Release Apache Paimon Terraform 0.1.0 (RC1)` email and send it to
+`[email protected]`. The vote stays open for at least 72 hours and passes
+with at least three binding `+1` votes and more binding `+1` than `-1` votes.
+
+If the candidate has problems, fix them on the release branch and cut the next
+candidate with `./dev/release/release_rc.sh <version> <next-rc>`. Never
+overwrite an existing candidate. After its vote is closed, delete the failed
+candidate from `dist/dev`.
+
+After 72 hours, reply to the vote thread with
+`[RESULT][VOTE] Release Apache Paimon Terraform 0.1.0 (RC1)`. State whether the
+vote passed, list binding and non-binding votes separately, and link the
+archived vote thread.
## Final release
@@ -84,23 +155,87 @@ After a successful vote:
./dev/release/release.sh 0.1.0 1
```
-The final script tags the exact RC commit, promotes the voted source artifact
to
-ASF `dist/release`, and creates the final GitHub Release from the already-voted
-assets. Nothing is rebuilt. Confirm the release appears in both registries,
-record it in the ASF reporter, and send an announcement to the Paimon mailing
-list.
+The script:
+
+1. Confirms the RC prerelease carries `SHA256SUMS`, a valid `SHA256SUMS.sig`,
+ and the manifest that the registries require.
+2. Creates a GPG-signed `v0.1.0` tag at the exact RC commit and pushes it.
+3. Moves the voted source release from `dist/dev` to `dist/release`.
+4. Creates the final GitHub Release from the voted source and binaries. Nothing
+ is rebuilt. Because the binaries are promoted, `go version -m` reports the
RC
+ tag they were built from; file names and Registry metadata carry the final
+ version, and those are what Terraform and OpenTofu resolve.
+5. Lists older patch releases of the same release line in `dist/release` and
+ prints the announcement email.
+
+## Post release
+
+1. Record the release at <https://reporter.apache.org/addrelease.html?paimon>.
+2. After each Registry has indexed the final version, verify installation from
+ a fresh directory without development overrides or a local plugin mirror:
+
+ ```bash
+ ./dev/release/verify_registry.sh 0.1.0 terraform
+ ./dev/release/verify_registry.sh 0.1.0 tofu
+ ```
+
+ The script pins the requested version, runs the CLI's normal Registry
+ download and checksum/signature verification, validates configuration, and
+ checks all five resource and two data-source schemas. It does not contact a
+ Catalog or create resources. A missing or unindexed release must fail this
+ check; do not substitute a local binary when recording distribution
+ evidence. Retain the output and the release signing-key identity with the
+ release verification.
+3. Wait at least 24 hours after publishing, following the Paimon project
+ convention, and confirm the source release is on
+ `downloads.apache.org/paimon/`. Then send the printed `[ANNOUNCE]` email as
+ plain text from your `@apache.org` address to `[email protected]` and
+ `[email protected]`.
+4. Open a PR against
[`apache/paimon-website`](https://github.com/apache/paimon-website)
+ that adds the release to the download page and release notes. Merge it only
+ after the 24-hour wait.
+5. Remove the older releases of the same line listed by `release.sh` from
+ `dist/release`; they remain available from `archive.apache.org`. Keep the
+ latest release of every other maintained line. Remove a line's last release
+ only after the community agrees that line is no longer maintained.
+6. Review the generated GitHub release notes and confirm the release is marked
+ as the latest release.
+
+## Verifying a release candidate
+
+Reviewers verify a candidate with:
+
+```bash
+./dev/release/verify_rc.sh 0.1.0 1
+```
+
+The script imports the Apache Paimon `KEYS` into a temporary keyring, verifies
+the source release signature and SHA-512 checksum from `dist/dev`, verifies the
+convenience binaries against their signed `SHA256SUMS`, runs Apache RAT and the
+binary license inventory check, and runs `make check` and the protocol
+acceptance tests from the extracted source. Go modules and build caches stay in
+the throwaway sandbox, which is kept for inspection when verification fails.
+
+Set `VERIFY_SIGN=0`, `VERIFY_BINARY=0`, `VERIFY_RAT=0`, or
+`VERIFY_ACCEPTANCE=0` to skip a stage you cannot run, and say so in your vote.
+`VERIFY_ARTIFACTS_DIR=<dir>` verifies a local copy of the source release.
+
+A valid signature from a key in `KEYS`, matching checksums, a clean RAT check,
+verified binaries, and a passing build and test run support a `+1`.
+
+## Set up a GPG key
-After each Registry has indexed the final version, verify installation from a
-fresh directory without development overrides or a local plugin mirror:
+Create a key as described in the
+[ASF OpenPGP guide](https://infra.apache.org/openpgp.html), then append it to
+the Paimon `KEYS` file (this requires PMC access):
```bash
-./dev/release/verify_registry.sh 0.1.0 terraform
-./dev/release/verify_registry.sh 0.1.0 tofu
+svn co --depth files https://dist.apache.org/repos/dist/release/paimon
paimon-dist
+cd paimon-dist
+(gpg --list-sigs <KEY_ID> && gpg --armor --export <KEY_ID>) >> KEYS
+svn commit -m "Add the release signing key for <YOUR NAME>"
```
-The script pins the requested version, runs the CLI's normal Registry download
-and checksum/signature verification, validates configuration, and checks all
-five resource and two data-source schemas. It does not contact a Catalog or
-create resources. A missing or unindexed release must fail this check; do not
-substitute a local binary when recording distribution evidence. Retain the
-output and the release signing-key identity with the release verification.
+Register the same public key with both registries as described in
+[Registry onboarding](#registry-onboarding-first-release-only), so they can
+verify `SHA256SUMS.sig`.