Files
doris/tools/release-tools
Mingyu Chen (Rayner) 4eec06aca7 [improvement](release) Harden the release helper scripts: signature reminder, mail drafts, idempotent completion, tag and GitHub release (#66663)
### What problem does this PR solve?

Issue Number: N/A

Related PR: #65488

Problem Summary:

Gaps in the release helper scripts, all found while preparing
4.0.8-rc02.

---

**1. `02-package-sign-upload.sh` signs the convenience binaries but
never uploads them**

The script writes `.asc` and `.sha512` next to each `BIN_FILES` entry
and stops there, while `03-vote-mail.sh` already advertises those files
under `BIN_DOWNLOAD_BASE`. Nothing told the RM to publish them, so a
vote mail could go to `dev@` with dead `.asc` links.

The reminder now prints on every successful exit, including the two
paths that answer `n` at the SVN confirmations — the binaries are
already signed by then, so the reminder is just as relevant there.

```
[WARN] the binary .asc signatures are NOT uploaded by this script.
Upload each binary together with its .asc and .sha512 to https://<base>/ :
  apache-doris-4.0.8-bin-x64.tar.gz  apache-doris-4.0.8-bin-x64.tar.gz.asc  apache-doris-4.0.8-bin-x64.tar.gz.sha512
  ...
03-vote-mail.sh advertises them under https://<base>/, so upload them BEFORE sending the vote email.
```

**2. `03-vote-mail.sh` produced an incomplete draft**

- The subject only reached the console and the `.eml` header, so the RM
had to take the subject from one place and the body from another.
`vote-email.txt` now starts with the `Subject:` line, and the whole mail
can be copied from a single file.
- The subject wording is now `[VOTE] Release for Apache Doris <tag>`,
matching what is used on the list.
- The body now ends with the RM's own `Here is my +1(binding)`, placed
above the signature, so it does not have to be typed in by hand every
release.

**3. `04-release-complete.sh` could only ever run once**

The first thing it did after checking the RC artifacts was:

```bash
if svn info "$RELEASE_SVN_DIR" >/dev/null 2>&1; then
  die "release SVN folder already exists: $RELEASE_SVN_DIR (use --mail-only to regenerate the email)"
fi
```

So once the release was published, every re-run died — including a
re-run right after the publish succeeded and the announce step failed,
which is exactly when the RM wants to continue. `--mail-only` was the
documented workaround, but it also skips the checks that would tell the
RM whether the publish actually completed.

It now discovers the dev and release SVN state first and converges from
there:

- all three release artifacts present: skip the publish steps, remove a
dev RC folder that was left behind, draft the email;
- nothing published: run the full flow, reusing a release directory that
exists but is empty instead of trying to create it again;
- some but not all release artifacts present: report exactly which files
are missing and stop, rather than guessing. `svnmucc` commits all of its
operations in one revision, so this state can only come from a manual
change and deserves a human.

The dev RC folder removal is also skipped when the folder is already
gone, so the `svnmucc rm` cannot fail a re-run.

**4. `04-release-complete.sh` now asks before every step**

There used to be exactly one confirmation, right before the `svnmucc`
commit. Each step now prints what it is about to do and waits:

```
== step 3: Publish to the release SVN and remove the dev RC folder ==
     This is public and requires PMC permission.
     All of it lands in one SVN revision:
     mkdir https://dist.apache.org/repos/dist/release/doris/4.0/4.0.8
     mv    .../dev/doris/4.0.8-rc02/apache-doris-4.0.8-rc02-src.tar.gz
       ->  .../release/doris/4.0/4.0.8/apache-doris-4.0.8-src.tar.gz
     ...
Proceed with step 3? [y/N]
```

Declining any step stops the run without changing further state and says
so, and because the script is idempotent the run can simply be started
again later.

**5. The announce draft carries its subject**

`announce-email.txt` now starts with a `Subject:` line matching the
header already written into `announce-email.eml`, the same treatment the
vote email gets above.

**6. `04-release-complete.sh` now finishes the release on GitHub too**

Releasing 4.0.8 from the `4.0.8-rc02` tag still needed two manual steps
after the SVN publish: pushing a `4.0.8` tag at the same commit, and
creating the GitHub release by hand in the web UI. Step 04 does both
now, and both are idempotent like the rest of the script.

The tag step only runs when `TAG` carries an `rc` suffix. It verifies
that an existing local or remote tag of that name sits on the RC commit,
stops if it does not, and skips itself once the tag is on the remote.

The release step never rewrites a published release. Its body follows
the shape every previous Doris release uses — change log link, download
page, release SVN source artifacts, and one `Binary(<arch>)` section per
`BIN_FILES` entry, linked to the public mirror rather than the host the
vote email advertises:

```
[Change Log](https://github.com/apache/doris/issues/66642)

- Official Downloads: https://doris.apache.org/download

- Source:
    - [apache-doris-4.0.8-src.tar.gz](https://dist.apache.org/repos/dist/release/doris/4.0/4.0.8/apache-doris-4.0.8-src.tar.gz) ([asc](...))([sha512](...))

- Binary(x64):
    - [apache-doris-4.0.8-bin-x64.tar.gz](https://download.velodb.io/apache-doris-4.0.8-bin-x64.tar.gz) ([asc](...))([sha512](...))
...
```

Before creating it, the script prints which release GitHub currently
marks Latest and asks whether this one should take that place, so a
4.0.x maintenance release does not displace a newer 4.1.x by accident:

```
The release currently marked Latest on apache/doris is 4.1.3.
Mark 4.0.8 as the Latest release? [y/N]
```

`gh` stays optional: a missing or unauthenticated `gh` warns and moves
on, and `--skip-github-release` pushes the tag while leaving the release
alone. `GITHUB_REPO` defaults to the `owner/repo` parsed out of the
`GIT_REMOTE` URL, credentials stripped.

**7. `git rev-parse` without `-q --verify`**

Plain `git rev-parse 4.0.8^{commit}` echoes its argument back and exits
non-zero when the tag does not exist, so `$(git rev-parse ... || true)`
captured the string `4.0.8^{commit}` instead of an empty result. The tag
step read that as a commit id and refused to tag a release that had
never been tagged. Every call passes `-q --verify` now, and the fake
`git` in the new test reproduces the real behaviour, so dropping the
flags fails the suite.

**8. A directory leak the existing test already caught**

`tests/test-release-complete-checksum.sh` fails on current master:

```
checksum temp directory was not removed after checksum verification failure: /var/folders/.../checksum-dir-32602-11908
```

`checksum_dir` was `local` to `publish_to_release_svn`, while the `trap
... EXIT` referencing it is global. When `set -e` aborted the run the
local was already out of scope, so the trap expanded it to an empty
string and `rm -rf ""` left the directory behind. It is a script-level
variable with a cleanup function now, and the test passes.

### Release note

None

### Check List (For Author)

- Test
    - [x] Unit Test

Three new cases and one updated case under `tools/release-tools/tests/`,
all following the existing stub-`release.env` plus
fake-binaries-on-`PATH` pattern already used by
`test-release-complete-checksum.sh`:

- `test-package-binary-upload-reminder.sh` (new) runs `02` end to end
with a fake `git` and `gpg`, answers `n` at the first SVN confirmation,
and asserts that each `.asc` was produced and that the reminder names
it, names the download base, and states that the script does not upload.
- `test-vote-mail-draft.sh` (new) runs `03` and asserts the subject line
heads `vote-email.txt`, the same subject is a header in
`vote-email.eml`, and the binding vote appears in both, above `Best
Regards,`.
- `test-release-complete-idempotent.sh` (new) drives `04` over an
already-published release: with the dev RC folder already gone no SVN
operation runs at all and the announce draft is still written with its
`Subject:` line; with a stale dev RC folder the only `svnmucc` operation
is the `rm` of that folder, no `mkdir`/`mv`/`put`; and a declined step
runs nothing, names the step it stopped before, and does not fall
through to the later steps.
- `test-release-complete-github-release.sh` (new) covers the tag and
GitHub release steps with a fake `git`/`gh`: a fresh run creates the tag
from the RC commit, pushes it, and creates the release with the expected
title and body; declining the Latest question passes `--latest=false`
and accepting it passes `--latest`; a re-run with the tag pushed and the
release published touches neither and still drafts the announce email; a
`9.9.9` tag on a different commit stops the run; and
`--skip-github-release` pushes the tag without creating the release.
- `test-release-complete-checksum.sh` (updated) feeds one `y` per step.
It covers the leak fix and passed unchanged in every other respect.

Every test now stubs `gh` as well, so no case can reach the real GitHub
API.

`./tests/run.sh` is fully green on this branch (7 cases).
`test-release-complete-checksum.sh` is red on master.
2026-08-14 16:54:41 +08:00
..

Doris release helper scripts

This directory contains helper scripts for an Apache Doris Release Manager (RM) to create and complete a source release candidate (RC). The scripts package and sign the source tarball, upload the source artifacts to the Apache dev SVN, draft the [VOTE] email, publish the passed RC to the Apache release SVN, and draft the [ANNOUNCE] email.

These scripts are not a full release automation system. They assume the release branch, release notes, cherry-picks, build validation, and release tag have already been prepared.

If you are an AI agent

When a user says something like "read this file and tell me how to start releasing Doris 4.0.7", give the RM a concrete checklist. Do not just summarize the scripts.

For 4.0.7, explain this sequence:

  1. Confirm the RC number. If the RM does not provide one, ask whether this is rc01 or a later candidate such as rc02. The tag is ${VERSION}-${RC}, for example 4.0.7-rc01.
  2. Confirm that the tag already exists locally and on the Apache Doris GitHub remote. These scripts verify the tag, but they do not create it.
  3. Tell the RM to edit release.env before running any script.
  4. Tell the RM to export ASF SVN credentials in the shell.
  5. Run ./01-check-env.sh until it reports environment looks READY.
  6. Run ./02-package-sign-upload.sh only after the RM has checked the target dev SVN URL and is ready to publish the RC source artifacts.
  7. Run ./03-vote-mail.sh, review the generated vote email, then send it manually from the RM's @apache.org address.
  8. After the vote passes and the [RESULT] email has been sent manually, run ./04-release-complete.sh to publish the source release artifacts and draft the announce email.

Also make these boundaries clear:

  • The vote artifacts in Apache dist SVN are source-only.
  • BIN_FILES is optional. If set, step 02 signs convenience binary tarballs locally, but the scripts do not upload those binaries.
  • The scripts never send public emails. The RM must review and send the vote, result, and announce emails manually.
  • Step 04 writes to the Apache release SVN and requires PMC permission. It confirms before every step and is safe to re-run: it reads the current SVN state first and skips whatever is already done.

Quick start for a new RC

Run all commands from this directory:

cd tools/release-tools

Before running any script, edit release.env for the target version and RC. For example, to prepare Doris 4.0.7-rc01:

VERSION="4.0.7"
RC="rc01"
TAG="${VERSION}-${RC}"
GIT_REMOTE="upstream-apache"

APACHE_ID="<your-apache-id>"
APACHE_EMAIL="<your-apache-id>@apache.org"
SIGNER_NAME="<your display name>"
SIGNING_KEY="<your signing key fingerprint, or leave empty if only one secret key exists>"

RELEASE_NOTES_URL="<release notes or issue URL>"
ANNOUNCE_RELEASE_NOTES_URL="<release notes URL, or leave empty to reuse/prompt>"

If you need to advertise convenience binaries in the vote email, set BIN_FILES to absolute paths for those prebuilt tarballs. Leave BIN_FILES=() for the source-only flow.

Export ASF credentials in the same shell. The scripts use these credentials for SVN upload and KEYS publishing:

export ASF_USERNAME=<your-apache-id>
export ASF_PASSWORD='<your-apache-ldap-password>'

Then run the release flow:

./01-check-env.sh
./02-package-sign-upload.sh
./03-vote-mail.sh

Review the generated vote email in WORK_DIR, then send it manually to dev@doris.apache.org from your @apache.org address.

After the vote passes:

  1. Tally the votes and send the [RESULT] email manually.
  2. Run ./04-release-complete.sh.
  3. Check that the new source artifacts are visible on downloads.apache.org.
  4. Update the Doris download page and release metadata as needed, then check that the public download page points at the new release.
  5. Wait for the download and CDN propagation window.
  6. Review the generated announce email in WORK_DIR.
  7. Only after those checks pass, send the [ANNOUNCE] email manually.

Use this command only when the release SVN step has already been done and you only need to regenerate the announce email:

./04-release-complete.sh --mail-only

Prerequisites

Have these ready before you run anything:

  • The release tag has already been created and pushed to apache/doris. Branch preparation, patch merges, validation, and tag creation are out of scope for these scripts.
  • The local Doris checkout has fetched that tag.
  • release.env is filled in for this exact release.
  • These tools are on PATH: git, gpg, svn, svnmucc, sha512sum, curl, and gzip.
  • A GPG signing key is available locally, or the RM is ready to let 01-check-env.sh import an existing key or generate and publish a new key.
  • ASF credentials are exported as ASF_USERNAME and ASF_PASSWORD.
  • The RM can write to the required Apache SVN locations. Step 04 requires PMC permission for the release SVN commit.

Configuration reference

All scripts source release.env. Edit it once per RC.

Set these fields first:

  • VERSION: Final release version, for example 4.0.7.
  • RC: Candidate number, for example rc01 or rc02.
  • TAG: Derived as ${VERSION}-${RC}. Do not set it to the final version without the RC suffix.
  • GIT_REMOTE: Git remote that points at github.com/apache/doris.
  • APACHE_ID: Apache committer id.
  • APACHE_EMAIL: Apache email address.
  • SIGNER_NAME: Display name used in generated emails.
  • SIGNING_KEY: Fingerprint passed to gpg -u. Leave it empty only when this machine has exactly one usable local secret key.
  • RELEASE_NOTES_URL: Link used in the vote email. If empty, step 03 prompts.
  • ANNOUNCE_RELEASE_NOTES_URL: Link used in the announce email. If empty, step 04 reuses RELEASE_NOTES_URL or prompts.
  • BIN_FILES: Optional absolute paths to prebuilt convenience binary tarballs.
  • RELEASE_TAG: RC-free tag step 04 pushes and publishes, ${VERSION} by default.
  • GITHUB_REPO: owner/repo for the GitHub release. Empty means parse it out of the GIT_REMOTE URL.
  • GITHUB_RELEASE_TITLE: Release title, Apache Doris <version> Release by default.
  • RELEASE_BIN_DOWNLOAD_BASE: Base URL the GitHub release body links the binaries to. This is the public mirror, not BIN_DOWNLOAD_BASE.

The default REPO_DIR is the enclosing Doris checkout because these scripts live under tools/release-tools. Override it only when you deliberately run the scripts against another clone.

The default SVN URLs target the official Doris dist repositories:

  • Dev RC source artifacts: https://dist.apache.org/repos/dist/dev/doris/<version>-<rc>/
  • Final source release artifacts: https://dist.apache.org/repos/dist/release/doris/<major.minor>/<version>/

Step 02 uploads source artifacts with the RC suffix:

  • apache-doris-<version>-<rc>-src.tar.gz
  • apache-doris-<version>-<rc>-src.tar.gz.asc
  • apache-doris-<version>-<rc>-src.tar.gz.sha512

Step 04 publishes final source artifacts without the RC suffix:

  • apache-doris-<version>-src.tar.gz
  • apache-doris-<version>-src.tar.gz.asc
  • apache-doris-<version>-src.tar.gz.sha512

All generated files go to WORK_DIR, which defaults to tools/release-tools/<version>-<rc>.

Script details

01-check-env.sh

Use this script to prepare and verify the signing environment. It checks required tools, GPG_TTY, GPG SHA512 digest preferences, the signing key, live published KEYS, test signing, and ASF credentials.

This script is mostly read-only. It prompts before every state-changing action, including editing gpg.conf, importing or generating a key, and publishing the key to the Doris KEYS files.

Expected success line:

environment looks READY for <version>-<rc>

02-package-sign-upload.sh

Use this script to build, sign, checksum, and upload the RC source artifacts.

It verifies that the local tag exists, the tag exists on GIT_REMOTE, and both tags point to the same commit. It then creates the source tarball with git archive, writes .asc and .sha512 sidecars, verifies both, and uploads the three source files to the Apache dev SVN.

If BIN_FILES is non-empty, this script also signs and checksums those binary tarballs in place. It does not upload binaries. When the run ends it reminds the RM to upload each binary together with its .asc and .sha512 to BIN_DOWNLOAD_BASE, because step 03 advertises them under that base in the vote email. Do that before sending the vote email, or the links will be dead.

This script pauses twice before touching the public dev SVN. Check the printed target URL before confirming.

03-vote-mail.sh

Use this script to generate the [VOTE] email draft. It writes:

  • vote-email.txt, starting with a Subject: line so the subject and the body can be copied from one file
  • vote-email.eml, carrying the same subject as a real mail header

The subject is [VOTE] Release for Apache Doris <version>-<rc>, and the body ends with the RM's own +1(binding) above the signature.

Review the draft and send it manually from the RM's @apache.org address to dev@doris.apache.org.

04-release-complete.sh

Use this script only after the vote has passed and the [RESULT] email has been sent manually.

Every step announces what it is about to do and waits for a y before running. Answering anything else stops the run without changing further state, and the run can be continued later by starting the script again.

The steps are:

  1. Inspect the dev and release SVN state. Read-only, and it decides what the later steps do.
  2. Verify the RC artifacts and build the final checksum. It downloads the RC tarball, signature and checksum, checks the sha512 and the detached signature that voters approved, and regenerates the checksum sidecar under the RC-free tarball name. Local only.
  3. Publish to the release SVN. One svnmucc revision creates the release directory, moves the source tarball and detached signature there, uploads the final checksum, and removes the dev RC folder.
  4. Tag the release. When TAG carries an rc suffix, the RC-free RELEASE_TAG is created at the same commit and pushed to GIT_REMOTE. Skipped when TAG has no rc suffix.
  5. Publish the GitHub release for that tag, with a body in the shape every previous Doris release uses: the change log link, the download page, and the source and binary artifacts. It prints which release GitHub currently marks Latest and asks whether this one should take that place, so a 4.0.x maintenance release does not displace a newer 4.1.x. Skipped with --skip-github-release, and skipped with a warning when gh is missing or not authenticated.
  6. Write the announce email draft.

The script is idempotent, so it is safe to re-run after a successful publish or after stopping at any prompt:

  • All three release artifacts already published: the publish steps are skipped. A dev RC folder left behind is removed, and the announce email is drafted.
  • Nothing published yet: the full flow runs. A release directory that exists but is empty is reused instead of being created again.
  • Some but not all release artifacts present: the script refuses to guess and reports exactly which files are missing. svnmucc commits all of its operations in one revision, so this state only comes from manual changes.
  • Release tag already pushed at the RC commit: the tagging step reports it and moves on. A tag of the same name on a different commit stops the run.
  • GitHub release already published: the step reports its URL and moves on. It never rewrites a published release.

It writes:

  • announce-email.txt, starting with a Subject: line so the subject and the body can be copied from one file
  • announce-email.eml, carrying the same subject as a real mail header

The subject is [ANNOUNCE] Apache Doris <version> release.

Before sending the announce email, check that the release is visible on downloads.apache.org, update and verify the Doris download page, and wait for the download and CDN propagation window. Only after those checks pass, send the [ANNOUNCE] email manually.

Use ./04-release-complete.sh --mail-only to regenerate the announce email without touching SVN.

Tests

Run the release-tool shell tests from this directory:

./tests/run.sh

Manual work not automated

The RM must still do these tasks manually:

  • Prepare the release branch and tag.
  • Validate the release before starting the RC upload flow.
  • Send the [VOTE] email.
  • Upload convenience binaries, if the release includes them.
  • Tally the vote and send the [RESULT] email.
  • Verify downloads.apache.org and the Doris download page before announcing.
  • Wait for the download and CDN propagation window.
  • Send the [ANNOUNCE] email.
  • Publish Maven staging repositories, update the website and GitHub release notes, and clean up old release versions when applicable.