diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6ac29a410..8557925b6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -49,5 +49,15 @@ repos:
"-j0",
"--rcfile=.pylintrc",
]
- exclude: ^(docs/|changelog.py)
+ exclude: ^(docs/)
require_serial: true
+
+ # Shell scripts: release tooling and repo githooks. Widen to all *.sh once legacy
+ # scripts under .buildkite/, recipes/, repo root, etc. pass shellcheck.
+ - repo: https://github.com/shellcheck-py/shellcheck-py
+ rev: v0.11.0.1
+ hooks:
+ - id: shellcheck
+ name: shellcheck
+ args: [-x]
+ files: (^scripts/release/.*\.sh$)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 50e403f78..f060d6a78 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -60,7 +60,7 @@ Once your changes and tests are ready to submit for review:
Ensure that all tests pass by running `make check-all`. This runs sequentially lint checks, unit tests and integration tests. These can be executed in isolation using `make lint`, `make test` and `make it` respectively, in case you need to iterate over a subset of tests.
- Note: Integration tests are much slower than unit tests and require `docker-compose`.
+ Note: Integration tests are much slower than unit tests and require `docker compose`. They also require **Java 21**; set `JAVA_HOME` (or `JAVA21_HOME`) to match.
3. Sign the Contributor License Agreement
@@ -79,6 +79,10 @@ Then sit back and wait. There will probably be discussion about the pull request
Note: Contributors belonging to the "Elastic" organization on Github can merge PRs themselves after getting a "LGTM" (Looks good to me); this workflow is similar to the established one in the Elasticsearch project.
+## Release process (maintainers)
+
+Release preparation is automated via `make release` and scripts under `scripts/release/`; see the Makefile and script headers for details.
+
# Contributing to the Rally codebase
**Repository:** [https://github.com/elastic/rally](https://github.com/elastic/rally)
diff --git a/Makefile b/Makefile
index 9d7dd6a57..cde9aa670 100644
--- a/Makefile
+++ b/Makefile
@@ -67,6 +67,7 @@ check-all: all
test-3.11 \
test-3.12 \
test-3.13 \
+ test-release \
it \
it_serverless \
it_tracks_compat \
@@ -143,7 +144,7 @@ lint: venv
precommit pre-commit: venv
uv run -- pre-commit run
-# It install a pre-commit hook in the project .git dir so modified files are checked before creating every commit.
+# It installs a pre-commit hook in the project .git dir so modified files are checked before creating every commit.
install-pre-commit: $(PRE_COMMIT_HOOK_PATH)
$(PRE_COMMIT_HOOK_PATH):
@@ -216,12 +217,18 @@ benchmark: venv
# --- Release goals ---
-release-checks: venv
- $(VENV_ACTIVATE); ./release-checks.sh $(release_version) $(next_version)
+# usage: e.g. make release-checks RELEASE_VERSION=X.Y.Z
+release-checks test-release: venv
+ @if [ -z "$(RELEASE_VERSION)" ]; then echo "error: set RELEASE_VERSION (e.g. make release-checks RELEASE_VERSION=X.Y.Z)" >&2; exit 1; fi
+ $(MAKE) clean lint test-all docs
+ uv run -- ./scripts/release/prepare.sh --dry "$(RELEASE_VERSION)"
-# usage: e.g. make release release_version=0.9.2 next_version=0.9.3
-release: venv release-checks clean docs lint test it
- $(VENV_ACTIVATE); ./release.sh $(release_version) $(next_version)
+# usage: e.g. make release RELEASE_VERSION=X.Y.Z
+release:
+ @if [ -z "$(RELEASE_VERSION)" ]; then echo "error: set RELEASE_VERSION (e.g. make release RELEASE_VERSION=X.Y.Z)" >&2; exit 1; fi
+ uv run -- ./scripts/release/prepare.sh "$(RELEASE_VERSION)"
+
+# --- Other goals ---
# This is a shortcut for creating a shell running inside the project virtual environment.
sh:
diff --git a/docs/developing.rst b/docs/developing.rst
index 0b41102d7..80478e872 100644
--- a/docs/developing.rst
+++ b/docs/developing.rst
@@ -8,6 +8,7 @@ Install the following software packages:
* `uv `_
* JDK version required to build Elasticsearch. Please refer to the `build setup requirements `_.
+ For running Rally's integration tests (e.g. ``make it`` or ``make it_tracks_compat``), ensure your environment uses **Java 21** (recent Rally versions use Java 21 in CI). Set ``JAVA_HOME`` or ``JAVA21_HOME`` accordingly.
* `Docker `_ and on Linux additionally `docker-compose `_.
* `jq `_
* git
@@ -36,7 +37,7 @@ Rally uses automatic code formatters. You can apply them by running ``make forma
However, consider using editor integrations to do it automatically: you'll need to configure `black `_ and `isort `_.
-Also consider running `pre-commit install` to run lint as part of your git commits.
+Also consider running ``make install-pre-commit`` to ensure quick verifications are being performed just before new Git commits are created.
Automatic Updates
~~~~~~~~~~~~~~~~~
diff --git a/esrally/utils/io.py b/esrally/utils/io.py
index 653a23c22..39cdb0a1e 100644
--- a/esrally/utils/io.py
+++ b/esrally/utils/io.py
@@ -24,6 +24,7 @@
import os
import shutil
import subprocess
+import sys
import tarfile
import zipfile
from collections.abc import Collection, Mapping, Sequence
@@ -397,7 +398,10 @@ def _do_decompress_manually_with_lib(target_directory: str, filename: str, compr
def _do_tar_decompress(target_directory: str, compressed_file: tarfile.TarFile) -> None:
try:
- compressed_file.extractall(path=target_directory, filter="tar")
+ if sys.version_info >= (3, 12):
+ compressed_file.extractall(path=target_directory, filter="tar")
+ else:
+ compressed_file.extractall(path=target_directory)
except Exception:
raise RuntimeError(f"Could not decompress provided archive [{compressed_file.name!r}]. Please check if it is a valid tar file.")
finally:
diff --git a/prepare-release.sh b/prepare-release.sh
deleted file mode 100755
index 7a71dddf1..000000000
--- a/prepare-release.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env bash
-
-# Licensed to Elasticsearch B.V. under one or more contributor
-# license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright
-# ownership. Elasticsearch B.V. 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.
-
-# fail this script immediately if any command fails with a non-zero exit code
-set -eu
-
-RELEASE_VERSION=$1
-__NOTICE_OUTPUT_FILE="NOTICE.txt"
-
-
-echo "============================="
-echo "Preparing Rally release $RELEASE_VERSION"
-echo "============================="
-
-echo "Preparing ${__NOTICE_OUTPUT_FILE}"
-source create-notice.sh
-
-echo "Updating author information"
-git log --format='%aN' | sort -fu > AUTHORS
-# This will produce a non-zero exit code iff there are changes.
-# Obviously we should disable exiting on error temporarily.
-set +e
-git diff --exit-code
-set -e
-
-echo "Updating changelog"
-# For exit on error to work we have to separate
-# CHANGELOG.md generation into two steps.
-CHANGELOG="$(python3 changelog.py ${RELEASE_VERSION})"
-printf "$CHANGELOG\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
-
-echo "Updating release version number"
-printf '__version__ = "%s"\n' $RELEASE_VERSION > esrally/_version.py
-git commit -a -m "Bump version to $RELEASE_VERSION"
-
-pip install --editable .
-
-# Check version
-if ! [[ $(esrally --version) =~ "esrally ${RELEASE_VERSION} (git revision" ]]
-then
- echo "ERROR: Rally version string [$(esrally --version)] does not start with expected version string [esrally $RELEASE_VERSION]"
- exit 2
-fi
-
-echo ""
-echo "===================="
-echo "Please open a pull request for ${RELEASE_VERSION}"
-echo "===================="
diff --git a/pyproject.toml b/pyproject.toml
index 33f106760..f1c58ceb7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,7 +35,7 @@ classifiers = [
]
################################################################################################
#
-# Adapt `create-notice.sh` whenever changing dependencies here.
+# Adapt `scripts/release/create-notice.sh` whenever changing dependencies here.
#
# That script grabs all license files so we include them in the notice file.
#
@@ -93,7 +93,7 @@ dependencies = [
"hatchling==1.6.0",
"wheel==0.46.2",
# License: MIT — Rally runs `python -m pip install` to install track dependencies (see esrally.track.loader).
- "pip>=24.0",
+ "pip==26.0.1",
]
[project.optional-dependencies]
diff --git a/release-checks.sh b/release-checks.sh
deleted file mode 100755
index 73e1d89c8..000000000
--- a/release-checks.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env bash
-
-# Licensed to Elasticsearch B.V. under one or more contributor
-# license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright
-# ownership. Elasticsearch B.V. 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.
-
-# fail this script immediately if any command fails with a non-zero exit code
-set -eu
-RELEASE_VERSION=$1
-
-KERNEL_NAME=$(uname -s)
-if [[ ${KERNEL_NAME} != *"Linux"* ]]
-then
- echo "Error: release needs to be run on a Linux workstation but you are running on ${KERNEL_NAME}."
- echo "Switch to a Linux workstation and try again."
- exit 1
-fi
-
-# test number of parameters
-if [[ $# != 2 ]]
-then
- echo "Usage: make release release_version=RELEASE_VERSION next_version=NEXT_VERSION"
- exit 1
-fi
-
-if ! git config user.signingkey >/dev/null
-then
- echo "Error: the variable user.signingkey is not configured for git on this system."
- echo "The release process requires a valid gpg key configured both locally and on GitHub."
- echo "Please follow the instructions in https://git-scm.com/book/id/v2/Git-Tools-Signing-Your-Work"
- echo "to set your gpg for git."
- exit 1
-fi
-
-if [[ ! -f ~/.github/rally_release_changelog.token ]]
-then
- echo "Error: didn't find a valid GitHub token in ~/.github/rally_release_changelog.token."
- echo "The release process requires a valid GitHub token."
- exit 1
-fi
-
-if [[ ! -v GPG_TTY ]]
-then
- echo "Error: env variable GPG_TTY is not set. Please execute export GPG_TTY=\$(tty)"
- exit 1
-fi
-
-ORIGIN_URL=$(git remote get-url --push origin)
-if [[ ${ORIGIN_URL} != *"elastic/rally"* ]]
-then
- echo "Error: the git remote [origin] does not point to Rally's main repo at elastic/rally but to [${ORIGIN_URL}]."
- exit 1
-fi
-
-# Check if there will be any errors during CHANGELOG.md generation
-CHANGELOG="$(python3 changelog.py ${RELEASE_VERSION})"
-
diff --git a/scripts/offline-install.sh b/scripts/offline-install.sh
index fc6999b10..f0a7713bf 100755
--- a/scripts/offline-install.sh
+++ b/scripts/offline-install.sh
@@ -70,7 +70,7 @@ function main {
echo "Preparing NOTICE file"
__NOTICE_OUTPUT_FILE="${ABSOLUTE_DOWNLOAD_BIN_DIR}/NOTICE.txt"
- source "${SCRIPT_SRC_HOME}/../create-notice.sh"
+ source "${SCRIPT_SRC_HOME}/release/create-notice.sh"
# create an offline install script
cat >"${install_script}" < 0:
print("There are [%d] open issues on milestone [%s]. Aborting..." % (milestone.open_issues, milestone_name), file=sys.stderr)
- exit(2)
+ sys.exit(2)
- print("### %s\n" % milestone_name)
+ print("changelog.py: generating changelog for milestone [%s] (markdown on stdout)" % milestone_name, file=sys.stderr)
+ print("### %s\n" % milestone_name, file=sys.stdout)
print_category("Highlights", prs(gh, milestone, with_labels="highlight"))
print_category(
@@ -112,4 +153,8 @@ def main():
if __name__ == "__main__":
- main()
+ try:
+ main()
+ except Exception as e:
+ print("changelog.py: error: %s" % e, file=sys.stderr)
+ sys.exit(1)
diff --git a/create-notice.sh b/scripts/release/create-notice.sh
similarity index 100%
rename from create-notice.sh
rename to scripts/release/create-notice.sh
diff --git a/scripts/release/prepare.sh b/scripts/release/prepare.sh
new file mode 100755
index 000000000..3d977df8a
--- /dev/null
+++ b/scripts/release/prepare.sh
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+
+# Licensed to Elasticsearch B.V. under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch B.V. 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.
+
+# fail this script immediately if any command fails with a non-zero exit code
+set -eu
+
+DRY_RUN=0
+if [[ "${1:-}" == "--dry" ]]; then
+ DRY_RUN=1
+ shift
+fi
+
+if [[ $# -ne 1 ]]; then
+ echo "usage: $0 [--dry] " >&2
+ echo "example: $0 2.13.0" >&2
+ echo "example: $0 --dry 2.13.0" >&2
+ exit 1
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+RELEASE_VERSION=$1
+
+echo "============================="
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ echo "Dry run: preparing Rally release $RELEASE_VERSION"
+else
+ echo "Preparing Rally release $RELEASE_VERSION"
+fi
+echo "============================="
+
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ _tmp_notice=$(mktemp)
+ trap 'rm -f "${_tmp_notice:-}"' EXIT
+ __NOTICE_OUTPUT_FILE="$_tmp_notice"
+ echo "Preparing notice (dry run → temporary file, not NOTICE.txt)" >&2
+else
+ __NOTICE_OUTPUT_FILE="NOTICE.txt"
+ echo "Preparing ${__NOTICE_OUTPUT_FILE}"
+fi
+# shellcheck source=scripts/release/create-notice.sh
+source "${SCRIPT_DIR}/create-notice.sh"
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ trap - EXIT
+ rm -f "$_tmp_notice"
+fi
+
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ echo "dry-run: skipping AUTHORS (requires git)." >&2
+else
+ echo "Updating author information"
+ git log --format='%aN' | sort -fu > AUTHORS
+fi
+
+echo "Updating changelog"
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ python3 "${SCRIPT_DIR}/changelog.py" --dry "${RELEASE_VERSION}" >/dev/null || {
+ _changelog_ec=$?
+ echo "error: changelog.py failed for milestone ${RELEASE_VERSION} (dry run); see messages above." >&2
+ exit "$_changelog_ec"
+ }
+else
+ # For exit on error we check the substitution explicitly (set -e is unreliable here).
+ CHANGELOG="$(python3 "${SCRIPT_DIR}/changelog.py" "${RELEASE_VERSION}")" || {
+ _changelog_ec=$?
+ echo "error: changelog.py failed for milestone ${RELEASE_VERSION}; see messages above." >&2
+ exit "$_changelog_ec"
+ }
+ printf '%s\n\n%s' "$CHANGELOG" "$(cat CHANGELOG.md)" > CHANGELOG.md
+fi
+
+if [[ "$DRY_RUN" -eq 1 ]]; then
+ echo "dry-run: would write esrally/_version.py: __version__ = \"$RELEASE_VERSION\"" >&2
+ echo "dry-run: skipping git add/commit, pip install, and esrally --version check." >&2
+ echo ""
+ echo "===================="
+ echo "Dry run finished for ${RELEASE_VERSION}"
+ echo "===================="
+ exit 0
+fi
+
+echo "Updating release version number"
+printf '__version__ = "%s"\n' "$RELEASE_VERSION" > esrally/_version.py
+
+# Stage only files this script generates. Avoids `git commit -a`, which would include any
+# other dirty tracked files if the tree was not clean.
+git add AUTHORS CHANGELOG.md esrally/_version.py
+while IFS= read -r staged; do
+ [[ -z "$staged" ]] && continue
+ case "$staged" in
+ AUTHORS|CHANGELOG.md|esrally/_version.py) ;;
+ *)
+ echo "error: staged file outside release allowlist: $staged" >&2
+ echo "Unstage unrelated changes (e.g. git restore --staged ) or start from a clean tree." >&2
+ exit 1
+ ;;
+ esac
+done < <(git diff --cached --name-only)
+
+# Non-empty PREPARE_RELEASE_NO_VERIFY adds --no-verify (e.g. scripts/release/prepare-docker.sh).
+git commit -m "Bump version to $RELEASE_VERSION" ${PREPARE_RELEASE_NO_VERIFY:+--no-verify}
+
+pip install --editable .
+
+# Check version
+_version_out=$(esrally --version)
+_version_needle="esrally ${RELEASE_VERSION} (git revision"
+case "$_version_out" in
+*"${_version_needle}"*) ;;
+*)
+ echo "ERROR: Rally version string [${_version_out}] does not start with expected version string [esrally ${RELEASE_VERSION}]"
+ exit 2
+ ;;
+esac
+
+echo ""
+echo "===================="
+echo "Please open a pull request for ${RELEASE_VERSION}"
+echo "===================="
diff --git a/uv.lock b/uv.lock
index bead37ad5..1c4e07c6e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -700,7 +700,7 @@ requires-dist = [
{ name = "jsonschema", specifier = "==3.1.1" },
{ name = "markupsafe", specifier = "==2.0.1" },
{ name = "mypy", marker = "extra == 'develop'", specifier = "==1.15.0" },
- { name = "pip", specifier = ">=24.0" },
+ { name = "pip", specifier = "==26.0.1" },
{ name = "pre-commit", marker = "extra == 'develop'", specifier = "==2.20.0" },
{ name = "psutil", specifier = "==5.9.4" },
{ name = "py-cpuinfo", specifier = "==7.0.0" },