Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions dev-tools/bump_main_minor_freeze.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,19 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/version_bump_lib.sh"

PYTHON="${PYTHON:-python3}"
VALIDATION_PY="${SCRIPT_DIR}/version_bump_validation.py"
UPDATE_BACKPORTRC_PY="${SCRIPT_DIR}/update_backportrc.py"
UPDATE_CATALOG_PY="${SCRIPT_DIR}/update_catalog_snapshot.py"
CREATE_PR_SH="${SCRIPT_DIR}/create_github_pull_request.sh"

# Snapshot before any release-branch checkout replaces worktree helpers.
HELPERS_DIR="$(version_bump_snapshot_helpers "$SCRIPT_DIR" \
version_bump_validation.py \
update_backportrc.py \
update_catalog_snapshot.py \
create_github_pull_request.sh \
ensure_github_cli.sh)"
trap 'rm -rf "${HELPERS_DIR}"' EXIT
VALIDATION_PY="${HELPERS_DIR}/version_bump_validation.py"
UPDATE_BACKPORTRC_PY="${HELPERS_DIR}/update_backportrc.py"
UPDATE_CATALOG_PY="${HELPERS_DIR}/update_catalog_snapshot.py"
CREATE_PR_SH="${HELPERS_DIR}/create_github_pull_request.sh"

: "${NEW_VERSION:?NEW_VERSION must be set}"
: "${BRANCH:?BRANCH must be set}"
Expand Down
15 changes: 13 additions & 2 deletions dev-tools/bump_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
# else — immediate gh pr merge --squash (legacy / escape hatch)
# Does not modify .backportrc.json (reserved for future release automation).
#
# Helpers used after `git checkout` of the release branch (validation + PR
# creation) are snapshotted from this script's directory first so an older
# branch tip cannot replace them mid-run.
#
# Environment:
# NEW_VERSION, BRANCH — required
# DRY_RUN — true to skip push and PR creation
Expand Down Expand Up @@ -51,8 +55,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/version_bump_lib.sh"

PYTHON="${PYTHON:-python3}"
VALIDATION_PY="${SCRIPT_DIR}/version_bump_validation.py"
CREATE_PR_SH="${SCRIPT_DIR}/create_github_pull_request.sh"

# Snapshot before any release-branch checkout replaces worktree helpers.
HELPERS_DIR="$(version_bump_snapshot_helpers "$SCRIPT_DIR" \
version_bump_validation.py \
create_github_pull_request.sh \
ensure_github_cli.sh)"
trap 'rm -rf "${HELPERS_DIR}"' EXIT
VALIDATION_PY="${HELPERS_DIR}/version_bump_validation.py"
CREATE_PR_SH="${HELPERS_DIR}/create_github_pull_request.sh"

: "${NEW_VERSION:?NEW_VERSION must be set}"
: "${BRANCH:?BRANCH must be set}"
Expand Down
98 changes: 98 additions & 0 deletions dev-tools/unittest/test_version_bump_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License
# 2.0 and the following additional limitation. Functionality enabled by the
# files subject to the Elastic License 2.0 may only be used in production when
# invoked by an Elasticsearch process with a license key installed that permits
# use of machine learning features. You may not use this file except in
# compliance with the Elastic License 2.0 and the foregoing additional
# limitation.

"""Tests for helpers in dev-tools/version_bump_lib.sh."""

from __future__ import annotations

import os
import subprocess
import textwrap
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parents[2]
_LIB_SH = _REPO_ROOT / "dev-tools" / "version_bump_lib.sh"


def _run_lib_snippet(snippet: str, *, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
script = textwrap.dedent(
f"""\
set -euo pipefail
# shellcheck source=../version_bump_lib.sh
source "{_LIB_SH}"
{snippet}
"""
)
run_env = os.environ.copy()
if env:
run_env.update(env)
return subprocess.run(
["bash", "-c", script],
check=False,
capture_output=True,
text=True,
env=run_env,
)


def test_snapshot_helpers_copies_files_and_is_independent_of_src(tmp_path: Path) -> None:
src = tmp_path / "src"
src.mkdir()
(src / "helper_a.sh").write_text("#!/bin/bash\necho a\n", encoding="utf-8")
(src / "helper_b.py").write_text("print('b')\n", encoding="utf-8")

completed = _run_lib_snippet(
f"""
dest=$(version_bump_snapshot_helpers "{src}" helper_a.sh helper_b.py)
printf '%s\\n' "$dest"
test -f "$dest/helper_a.sh"
test -f "$dest/helper_b.py"
test -x "$dest/helper_a.sh"
# Mutating the source must not affect the snapshot.
echo mutated > "{src}/helper_a.sh"
grep -q mutated "$dest/helper_a.sh" && exit 2 || true
rm -rf "$dest"
"""
)
assert completed.returncode == 0, completed.stderr
dest_line = completed.stdout.strip().splitlines()[-1]
assert dest_line
assert not Path(dest_line).exists()


def test_snapshot_helpers_fails_on_missing_file(tmp_path: Path) -> None:
src = tmp_path / "src"
src.mkdir()
(src / "present.sh").write_text("#!/bin/bash\n", encoding="utf-8")

completed = _run_lib_snippet(
f'version_bump_snapshot_helpers "{src}" present.sh missing.sh'
)
assert completed.returncode != 0
assert "missing helper to snapshot" in completed.stderr


def test_create_github_pull_request_in_snapshot_finds_ensure_github_cli() -> None:
"""Regression: after checkout, create_pr must resolve ensure_github_cli next to itself."""
src = _REPO_ROOT / "dev-tools"
completed = _run_lib_snippet(
f"""
dest=$(version_bump_snapshot_helpers "{src}" \\
create_github_pull_request.sh \\
ensure_github_cli.sh)
bash -n "$dest/create_github_pull_request.sh"
# Snapshot must keep modern --label parsing (missing on older release branches).
grep -q -- '--label)' "$dest/create_github_pull_request.sh"
# ensure_github_cli must sit beside it for SCRIPT_DIR lookup.
test -f "$dest/ensure_github_cli.sh"
rm -rf "$dest"
"""
)
assert completed.returncode == 0, completed.stderr
33 changes: 33 additions & 0 deletions dev-tools/version_bump_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,36 @@ read_elasticsearch_version_from_ref() {
local ref=$1
git show "${ref}:gradle.properties" | grep '^elasticsearchVersion=' | head -1 | cut -d= -f2 | tr -d '[:space:]' || true
}

# Copy named helper files from \p src_dir into a fresh temp directory and print
# that path. Patch/minor bump pipelines check out the *target* release branch
# tip after starting from main; without a snapshot, later subprocesses would
# execute older copies of these helpers from the release branch (e.g. a
# create_github_pull_request.sh that does not accept --label).
#
# Usage: dest=$(version_bump_snapshot_helpers "$SCRIPT_DIR" file1 file2 ...)
version_bump_snapshot_helpers() {
local src_dir="$1"
shift
if [[ $# -lt 1 ]]; then
echo "ERROR: version_bump_snapshot_helpers requires at least one file name" >&2
return 1
fi
local dest name
dest="$(mktemp -d "${TMPDIR:-/tmp}/ml-cpp-version-bump-helpers.XXXXXX")"
for name in "$@"; do
if [[ ! -f "${src_dir}/${name}" ]]; then
echo "ERROR: missing helper to snapshot: ${src_dir}/${name}" >&2
rm -rf "${dest}"
return 1
fi
# Handle cp/chmod failure explicitly: under `set -e` an aborted copy would
# skip the caller's cleanup trap (not yet installed) and leak ${dest}.
if ! cp "${src_dir}/${name}" "${dest}/${name}" || ! chmod +x "${dest}/${name}"; then
echo "ERROR: failed to stage helper ${name} into ${dest}" >&2
rm -rf "${dest}"
return 1
fi
done
printf '%s' "$dest"
}