From 98f17d516d0148e5365ca7da9148b5d5640cd422 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 11:25:58 +1200 Subject: [PATCH 1/8] [ML] Automate patch version bump in CI pipeline Replace the version-bump pipeline stub with a patch-only flow: Slack notification, Wolfi step running dev-tools/bump_version.sh to bump elasticsearchVersion on BRANCH (and .backportrc.json on main), then json-watcher polling for staging and snapshot artifact versions. Supports DRY_RUN=true to skip git push. Minor-branch automation will follow in a separate change. Made-with: Cursor --- .buildkite/job-version-bump.json.py | 117 ++++++++++++++------------ dev-tools/bump_version.sh | 124 ++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 52 deletions(-) create mode 100755 dev-tools/bump_version.sh diff --git a/.buildkite/job-version-bump.json.py b/.buildkite/job-version-bump.json.py index 61f763987..138ba1ac8 100755 --- a/.buildkite/job-version-bump.json.py +++ b/.buildkite/job-version-bump.json.py @@ -10,83 +10,96 @@ # # This script generates JSON for the ml-cpp version bump pipeline. # It is intended to be triggered by the centralized release-eng pipeline. -# It can be integrated into existing or new workflows and includes a plugin -# that polls artifact URLs until the expected version is available. +# +# Patch workflow: bump version on BRANCH, then wait for staging and snapshot +# artifact JSON to publish NEW_VERSION. import contextlib import json +WOLFI_IMAGE = "docker.elastic.co/release-eng/wolfi-build-essential-release-eng:latest" +STAGING_URL = "https://artifacts-staging.elastic.co/ml-cpp/latest" +SNAPSHOT_URL = "https://storage.googleapis.com/elastic-artifacts-snapshot/ml-cpp/latest" + + +def json_watcher_plugin(url, expected_value): + return { + "elastic/json-watcher#v1.0.0": { + "url": url, + "field": ".version", + "expected_value": expected_value, + "polling_interval": "30", + } + } + + +def dra_step(label, key, depends_on, plugins): + return { + "label": label, + "key": key, + "depends_on": depends_on, + "agents": { + "image": WOLFI_IMAGE, + "cpu": "250m", + "memory": "512Mi", + "ephemeralStorage": "1Gi", + }, + "command": [ + 'echo "Waiting for DRA artifacts..."', + ], + "timeout_in_minutes": 240, + "retry": { + "automatic": [{"exit_status": "*", "limit": 2}], + "manual": {"permit_on_passed": True}, + }, + "plugins": plugins, + } + + def main(): - pipeline = {} - # TODO: replace the block step with version bump logic pipeline_steps = [ { "label": "Queue a :slack: notification for the pipeline", "depends_on": None, - "command": ".buildkite/pipelines/send_version_bump_notification.sh | buildkite-agent pipeline upload", + "command": ".buildkite/pipelines/send_slack_notification.sh | buildkite-agent pipeline upload", "agents": { "image": "python", }, }, { - "block": "Ready to fetch for DRA artifacts?", - "prompt": ( - "Unblock when your team is ready to proceed.\n\n" - "Trigger parameters:\n" - "- NEW_VERSION: ${NEW_VERSION}\n" - "- BRANCH: ${BRANCH}\n" - "- WORKFLOW: ${WORKFLOW}\n" - ), - "key": "block-get-dra-artifacts", - "blocked_state": "running", - }, - { - "label": "Fetch DRA Artifacts", - "key": "fetch-dra-artifacts", - "depends_on": "block-get-dra-artifacts", + "label": "Bump version to ${NEW_VERSION}", + "key": "bump-version", "agents": { - "image": "docker.elastic.co/release-eng/wolfi-build-essential-release-eng:latest", + "image": WOLFI_IMAGE, "cpu": "250m", "memory": "512Mi", - "ephemeralStorage": "1Gi", }, "command": [ - 'echo "Starting DRA artifacts retrieval..."', - ], - "timeout_in_minutes": 240, - "retry": { - "automatic": [ - { - "exit_status": "*", - "limit": 2, - } - ], - "manual": {"permit_on_passed": True}, - }, - "plugins": [ - { - "elastic/json-watcher#v1.0.0": { - "url": "https://artifacts-staging.elastic.co/ml-cpp/latest/${BRANCH}.json", - "field": ".version", - "expected_value": "${NEW_VERSION}", - "polling_interval": "30", - } - }, - { - "elastic/json-watcher#v1.0.0": { - "url": "https://storage.googleapis.com/elastic-artifacts-snapshot/ml-cpp/latest/${BRANCH}.json", - "field": ".version", - "expected_value": "${NEW_VERSION}-SNAPSHOT", - "polling_interval": "30", - } - }, + "dev-tools/bump_version.sh", ], }, + dra_step( + label="Fetch DRA Artifacts", + key="fetch-dra-artifacts", + depends_on="bump-version", + plugins=[ + json_watcher_plugin( + f"{STAGING_URL}/${{BRANCH}}.json", + "${NEW_VERSION}", + ), + json_watcher_plugin( + f"{SNAPSHOT_URL}/${{BRANCH}}.json", + "${NEW_VERSION}-SNAPSHOT", + ), + ], + ), ] - pipeline["steps"] = pipeline_steps + pipeline = { + "steps": pipeline_steps, + } print(json.dumps(pipeline, indent=2)) diff --git a/dev-tools/bump_version.sh b/dev-tools/bump_version.sh new file mode 100755 index 000000000..625338b4f --- /dev/null +++ b/dev-tools/bump_version.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# +# 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. +# +# Automated patch version bump for the release-eng pipeline. +# +# Updates elasticsearchVersion in gradle.properties to NEW_VERSION on BRANCH, +# updates .backportrc.json when bumping main, commits, and pushes. +# +# Set DRY_RUN=true to perform all steps except git push. +# +# Follows the same pattern as the Elasticsearch repo's automated +# Lucene snapshot updates (.buildkite/scripts/lucene-snapshot/). + +set -euo pipefail + +: "${NEW_VERSION:?NEW_VERSION must be set}" +: "${BRANCH:?BRANCH must be set}" +DRY_RUN="${DRY_RUN:-false}" + +GRADLE_PROPS="gradle.properties" +BACKPORT_CONFIG=".backportrc.json" + +if [ "$DRY_RUN" = "true" ]; then + echo "=== DRY RUN MODE — will not push ===" +fi + +git_push() { + local target_branch="$1" + if [ "$DRY_RUN" = "true" ]; then + echo " [DRY RUN] Would push $target_branch" + else + git push origin "$target_branch" + echo " Pushed $target_branch" + fi +} + +sed_inplace() { + if sed --version >/dev/null 2>&1; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +configure_git() { + git config user.name elasticsearchmachine + git config user.email 'infra-root+elasticsearchmachine@elastic.co' +} + +bump_version_on_branch() { + local target_branch="$1" + local target_version="$2" + + git checkout "$target_branch" + git pull --ff-only origin "$target_branch" + + local current_version + current_version=$(grep '^elasticsearchVersion=' "$GRADLE_PROPS" | cut -d= -f2) + if [ "$current_version" = "$target_version" ]; then + echo "Version on $target_branch is already $target_version — nothing to do" + return 0 + fi + + echo "Bumping version on $target_branch: $current_version → $target_version" + sed_inplace "s/^elasticsearchVersion=.*/elasticsearchVersion=${target_version}/" "$GRADLE_PROPS" + + if ! grep -q "^elasticsearchVersion=${target_version}$" "$GRADLE_PROPS"; then + echo "ERROR: version update verification failed on $target_branch" + grep 'elasticsearchVersion' "$GRADLE_PROPS" + exit 1 + fi + + # Update .backportrc.json so the new version maps to main + if [[ "$target_branch" == "main" && -f "$BACKPORT_CONFIG" ]]; then + echo "Updating backport config: v${target_version} → main" + # Use python for a reliable cross-platform JSON-safe replacement + python3 -c " +import json, re, sys +with open('$BACKPORT_CONFIG') as f: + data = json.load(f) +mapping = data.get('branchLabelMapping', {}) +new_mapping = {} +for k, v in mapping.items(): + if v == 'main' and re.match(r'\^v\d+\.\d+\.\d+\\\$', k): + new_mapping['^v${target_version}\$'] = 'main' + else: + new_mapping[k] = v +data['branchLabelMapping'] = new_mapping +with open('$BACKPORT_CONFIG', 'w') as f: + json.dump(data, f, indent=2) + f.write('\n') +" || echo "WARNING: could not update backport config — please check $BACKPORT_CONFIG manually" + fi + + if git diff-index --quiet HEAD --; then + echo "No changes to commit on $target_branch (file unchanged after sed)" + return 0 + fi + + configure_git + git add "$GRADLE_PROPS" "$BACKPORT_CONFIG" + git commit -m "[ML] Bump version to ${target_version}" + git_push "$target_branch" +} + +echo "=== Patch version bump: $BRANCH → $NEW_VERSION ===" +bump_version_on_branch "$BRANCH" "$NEW_VERSION" + +if [ "$DRY_RUN" = "true" ]; then + echo "" + echo "=== DRY RUN SUMMARY ===" + echo "Branch: $BRANCH" + echo "Version: $NEW_VERSION" + echo "Recent commits:" + git log --oneline -3 +fi From c05bf15366c7293083bf675a329c0c230a612605 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 11:54:03 +1200 Subject: [PATCH 2/8] [ML] Version bump Slack: test banner + optional skip DRA wait - Add send_slack_version_bump_notification.sh (version-bump pipeline only) with ML_CPP_VERSION_BUMP_TEST_MODE banner and optional channel override. - Wire job-version-bump.json.py to use it; support ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT for short smoke runs without json-watcher polling. Made-with: Cursor --- .buildkite/job-version-bump.json.py | 40 +++++++++------ .../send_slack_version_bump_notification.sh | 50 +++++++++++++++++++ 2 files changed, 74 insertions(+), 16 deletions(-) create mode 100755 .buildkite/pipelines/send_slack_version_bump_notification.sh diff --git a/.buildkite/job-version-bump.json.py b/.buildkite/job-version-bump.json.py index 138ba1ac8..9daedc53f 100755 --- a/.buildkite/job-version-bump.json.py +++ b/.buildkite/job-version-bump.json.py @@ -17,6 +17,7 @@ import contextlib import json +import os WOLFI_IMAGE = "docker.elastic.co/release-eng/wolfi-build-essential-release-eng:latest" @@ -63,7 +64,7 @@ def main(): { "label": "Queue a :slack: notification for the pipeline", "depends_on": None, - "command": ".buildkite/pipelines/send_slack_notification.sh | buildkite-agent pipeline upload", + "command": ".buildkite/pipelines/send_slack_version_bump_notification.sh | buildkite-agent pipeline upload", "agents": { "image": "python", }, @@ -80,23 +81,30 @@ def main(): "dev-tools/bump_version.sh", ], }, - dra_step( - label="Fetch DRA Artifacts", - key="fetch-dra-artifacts", - depends_on="bump-version", - plugins=[ - json_watcher_plugin( - f"{STAGING_URL}/${{BRANCH}}.json", - "${NEW_VERSION}", - ), - json_watcher_plugin( - f"{SNAPSHOT_URL}/${{BRANCH}}.json", - "${NEW_VERSION}-SNAPSHOT", - ), - ], - ), ] + # Smoke tests: set ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT on the Buildkite build + # to skip json-watcher polling (avoids a long-running build when NEW_VERSION + # will never appear in artifact JSON). + if not os.environ.get("ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT", "").strip(): + pipeline_steps.append( + dra_step( + label="Fetch DRA Artifacts", + key="fetch-dra-artifacts", + depends_on="bump-version", + plugins=[ + json_watcher_plugin( + f"{STAGING_URL}/${{BRANCH}}.json", + "${NEW_VERSION}", + ), + json_watcher_plugin( + f"{SNAPSHOT_URL}/${{BRANCH}}.json", + "${NEW_VERSION}-SNAPSHOT", + ), + ], + ) + ) + pipeline = { "steps": pipeline_steps, } diff --git a/.buildkite/pipelines/send_slack_version_bump_notification.sh b/.buildkite/pipelines/send_slack_version_bump_notification.sh new file mode 100755 index 000000000..e0f094f84 --- /dev/null +++ b/.buildkite/pipelines/send_slack_version_bump_notification.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# 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. +# +# Slack notifications for the ml-cpp-version-bump pipeline only (not PR builds). +# Set ML_CPP_VERSION_BUMP_TEST_MODE to any non-empty value to prepend a loud +# "TEST RUN" banner and optional custom channel (see below). +# +# Optional env: +# ML_CPP_VERSION_BUMP_TEST_MODE — non-empty => test banner + wording +# ML_CPP_VERSION_BUMP_SLACK_CHANNEL — override channel (default #machine-learn-build) + +CHANNEL="${ML_CPP_VERSION_BUMP_SLACK_CHANNEL:-#machine-learn-build}" + +if [ -n "${ML_CPP_VERSION_BUMP_TEST_MODE:-}" ]; then + TEST_LINES=' :rotating_light: **TEST RUN — ml-cpp version bump pipeline** :rotating_light: + _This is not a production release._ (ML_CPP_VERSION_BUMP_TEST_MODE is set on the build.) + Set ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT on the build to skip artifact polling for short smoke tests. + +' +else + TEST_LINES="" +fi + +cat < Date: Fri, 1 May 2026 12:28:34 +1200 Subject: [PATCH 3/8] [ML] TEMP: allow version-bump pipeline on PR test branch Widen ml-cpp-version-bump branch_configuration for PR #3030 smoke tests; revert to main-only after validation. Made-with: Cursor --- catalog-info.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index d472b4eee..22e5a837c 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -254,7 +254,9 @@ spec: description: Buildkite Pipeline for ml-cpp version bump spec: allow_rebuilds: true - branch_configuration: main + # TEMPORARY (PR #3030): allow Buildkite API/UI test runs from this branch. + # Revert to `main` only after the version-bump smoke test is complete. + branch_configuration: main feature/version-bump-patch-only cancel_intermediate_builds: false clone_method: https pipeline_file: .buildkite/job-version-bump.json.py From 8260305379d9c2225cbdfc4d6f8f87fb106705eb Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 12:31:25 +1200 Subject: [PATCH 4/8] [ML] Remove temp version-bump branch filter from patch bump PR The temporary ml-cpp-version-bump branch_configuration change is proposed separately so PR #3030 stays focused on the bump automation. Made-with: Cursor --- catalog-info.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index 22e5a837c..d472b4eee 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -254,9 +254,7 @@ spec: description: Buildkite Pipeline for ml-cpp version bump spec: allow_rebuilds: true - # TEMPORARY (PR #3030): allow Buildkite API/UI test runs from this branch. - # Revert to `main` only after the version-bump smoke test is complete. - branch_configuration: main feature/version-bump-patch-only + branch_configuration: main cancel_intermediate_builds: false clone_method: https pipeline_file: .buildkite/job-version-bump.json.py From e873f66702989ad231a2f960a32a7e495b824ccf Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 14:10:48 +1200 Subject: [PATCH 5/8] [ML] Trim bump_version.sh: drop .backportrc.json handling Patch-only version bumps update gradle.properties only; main/minor backport mapping stays out of this PR. Made-with: Cursor --- dev-tools/bump_version.sh | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/dev-tools/bump_version.sh b/dev-tools/bump_version.sh index 625338b4f..e8cbc7696 100755 --- a/dev-tools/bump_version.sh +++ b/dev-tools/bump_version.sh @@ -12,7 +12,8 @@ # Automated patch version bump for the release-eng pipeline. # # Updates elasticsearchVersion in gradle.properties to NEW_VERSION on BRANCH, -# updates .backportrc.json when bumping main, commits, and pushes. +# commits, and pushes. Does not modify .backportrc.json (reserved for a future +# main / minor bump automation change). # # Set DRY_RUN=true to perform all steps except git push. # @@ -26,7 +27,6 @@ set -euo pipefail DRY_RUN="${DRY_RUN:-false}" GRADLE_PROPS="gradle.properties" -BACKPORT_CONFIG=".backportrc.json" if [ "$DRY_RUN" = "true" ]; then echo "=== DRY RUN MODE — will not push ===" @@ -78,35 +78,13 @@ bump_version_on_branch() { exit 1 fi - # Update .backportrc.json so the new version maps to main - if [[ "$target_branch" == "main" && -f "$BACKPORT_CONFIG" ]]; then - echo "Updating backport config: v${target_version} → main" - # Use python for a reliable cross-platform JSON-safe replacement - python3 -c " -import json, re, sys -with open('$BACKPORT_CONFIG') as f: - data = json.load(f) -mapping = data.get('branchLabelMapping', {}) -new_mapping = {} -for k, v in mapping.items(): - if v == 'main' and re.match(r'\^v\d+\.\d+\.\d+\\\$', k): - new_mapping['^v${target_version}\$'] = 'main' - else: - new_mapping[k] = v -data['branchLabelMapping'] = new_mapping -with open('$BACKPORT_CONFIG', 'w') as f: - json.dump(data, f, indent=2) - f.write('\n') -" || echo "WARNING: could not update backport config — please check $BACKPORT_CONFIG manually" - fi - if git diff-index --quiet HEAD --; then echo "No changes to commit on $target_branch (file unchanged after sed)" return 0 fi configure_git - git add "$GRADLE_PROPS" "$BACKPORT_CONFIG" + git add "$GRADLE_PROPS" git commit -m "[ML] Bump version to ${target_version}" git_push "$target_branch" } From 17ea220bfd7477e4bed6f7d5cad5ce29c24b1404 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 14:13:35 +1200 Subject: [PATCH 6/8] [ML] Trim version-bump smoke hooks; keep DRY_RUN Remove ML_CPP_VERSION_BUMP_TEST_MODE / TEST banner and ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT (DRA wait steps always run). DRY_RUN remains a normal Buildkite env for bump_version.sh. Optional ML_CPP_VERSION_BUMP_SLACK_CHANNEL retained for routing. Made-with: Cursor --- .buildkite/job-version-bump.json.py | 37 ++++++++----------- .../send_slack_version_bump_notification.sh | 15 +------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/.buildkite/job-version-bump.json.py b/.buildkite/job-version-bump.json.py index 9daedc53f..07284b55f 100755 --- a/.buildkite/job-version-bump.json.py +++ b/.buildkite/job-version-bump.json.py @@ -81,30 +81,23 @@ def main(): "dev-tools/bump_version.sh", ], }, + dra_step( + label="Fetch DRA Artifacts", + key="fetch-dra-artifacts", + depends_on="bump-version", + plugins=[ + json_watcher_plugin( + f"{STAGING_URL}/${{BRANCH}}.json", + "${NEW_VERSION}", + ), + json_watcher_plugin( + f"{SNAPSHOT_URL}/${{BRANCH}}.json", + "${NEW_VERSION}-SNAPSHOT", + ), + ], + ), ] - # Smoke tests: set ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT on the Buildkite build - # to skip json-watcher polling (avoids a long-running build when NEW_VERSION - # will never appear in artifact JSON). - if not os.environ.get("ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT", "").strip(): - pipeline_steps.append( - dra_step( - label="Fetch DRA Artifacts", - key="fetch-dra-artifacts", - depends_on="bump-version", - plugins=[ - json_watcher_plugin( - f"{STAGING_URL}/${{BRANCH}}.json", - "${NEW_VERSION}", - ), - json_watcher_plugin( - f"{SNAPSHOT_URL}/${{BRANCH}}.json", - "${NEW_VERSION}-SNAPSHOT", - ), - ], - ) - ) - pipeline = { "steps": pipeline_steps, } diff --git a/.buildkite/pipelines/send_slack_version_bump_notification.sh b/.buildkite/pipelines/send_slack_version_bump_notification.sh index e0f094f84..3e99d0c97 100755 --- a/.buildkite/pipelines/send_slack_version_bump_notification.sh +++ b/.buildkite/pipelines/send_slack_version_bump_notification.sh @@ -9,25 +9,12 @@ # limitation. # # Slack notifications for the ml-cpp-version-bump pipeline only (not PR builds). -# Set ML_CPP_VERSION_BUMP_TEST_MODE to any non-empty value to prepend a loud -# "TEST RUN" banner and optional custom channel (see below). # # Optional env: -# ML_CPP_VERSION_BUMP_TEST_MODE — non-empty => test banner + wording # ML_CPP_VERSION_BUMP_SLACK_CHANNEL — override channel (default #machine-learn-build) CHANNEL="${ML_CPP_VERSION_BUMP_SLACK_CHANNEL:-#machine-learn-build}" -if [ -n "${ML_CPP_VERSION_BUMP_TEST_MODE:-}" ]; then - TEST_LINES=' :rotating_light: **TEST RUN — ml-cpp version bump pipeline** :rotating_light: - _This is not a production release._ (ML_CPP_VERSION_BUMP_TEST_MODE is set on the build.) - Set ML_CPP_VERSION_BUMP_SKIP_DRA_WAIT on the build to skip artifact polling for short smoke tests. - -' -else - TEST_LINES="" -fi - cat < Date: Fri, 1 May 2026 14:20:09 +1200 Subject: [PATCH 7/8] [ML] Add git push --dry-run auth probe for version bump pipeline Runs before Slack queue and bump so CI credentials are validated without creating or updating remote refs. Made-with: Cursor --- .buildkite/job-version-bump.json.py | 21 ++++++++++++--- dev-tools/git_push_auth_probe.sh | 41 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100755 dev-tools/git_push_auth_probe.sh diff --git a/.buildkite/job-version-bump.json.py b/.buildkite/job-version-bump.json.py index 07284b55f..5d5d17fef 100755 --- a/.buildkite/job-version-bump.json.py +++ b/.buildkite/job-version-bump.json.py @@ -11,8 +11,8 @@ # This script generates JSON for the ml-cpp version bump pipeline. # It is intended to be triggered by the centralized release-eng pipeline. # -# Patch workflow: bump version on BRANCH, then wait for staging and snapshot -# artifact JSON to publish NEW_VERSION. +# Patch workflow: verify git push credentials (dry-run), bump version on BRANCH, +# then wait for staging and snapshot artifact JSON to publish NEW_VERSION. import contextlib @@ -62,8 +62,22 @@ def dra_step(label, key, depends_on, plugins): def main(): pipeline_steps = [ { - "label": "Queue a :slack: notification for the pipeline", + "label": "Verify git push credentials (dry-run)", + "key": "git-push-auth-probe", "depends_on": None, + "agents": { + "image": WOLFI_IMAGE, + "cpu": "250m", + "memory": "512Mi", + }, + "command": [ + "dev-tools/git_push_auth_probe.sh", + ], + }, + { + "label": "Queue a :slack: notification for the pipeline", + "key": "queue-slack-notify", + "depends_on": "git-push-auth-probe", "command": ".buildkite/pipelines/send_slack_version_bump_notification.sh | buildkite-agent pipeline upload", "agents": { "image": "python", @@ -72,6 +86,7 @@ def main(): { "label": "Bump version to ${NEW_VERSION}", "key": "bump-version", + "depends_on": "queue-slack-notify", "agents": { "image": WOLFI_IMAGE, "cpu": "250m", diff --git a/dev-tools/git_push_auth_probe.sh b/dev-tools/git_push_auth_probe.sh new file mode 100755 index 000000000..7059d3fc2 --- /dev/null +++ b/dev-tools/git_push_auth_probe.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# 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. +# +# Verifies that the current checkout can authenticate for git push to origin, +# without updating any remote refs (uses "git push --dry-run"). +# +# Intended for the ml-cpp-version-bump Buildkite pipeline (same agent + remotes +# as dev-tools/bump_version.sh). Uses a disposable ref name under refs/heads/ci/ +# so it does not collide with release branches. +# +# Environment: +# BUILDKITE_BUILD_NUMBER — used to uniquify the probe ref (defaults to "local" +# when unset, e.g. manual runs outside Buildkite). +# GIT_REMOTE — remote name (default: origin). + +set -euo pipefail + +REMOTE="${GIT_REMOTE:-origin}" +BUILD_NUM="${BUILDKITE_BUILD_NUMBER:-local}" +PROBE_REF="refs/heads/ci/ml-cpp-bump-push-probe-${BUILD_NUM}" + +echo "=== Git push auth probe (dry-run; no remote refs updated) ===" +echo "Remote: ${REMOTE}" +echo "Local HEAD: $(git rev-parse HEAD)" +echo "Probe refspec: HEAD:${PROBE_REF}" +git remote -v + +if ! git push --dry-run "${REMOTE}" "HEAD:${PROBE_REF}"; then + echo "ERROR: git push --dry-run failed — check credentials and GitHub permissions for ${REMOTE}." >&2 + exit 1 +fi + +echo "OK: git push --dry-run succeeded for ${REMOTE}." From e50edc60dc93650438c208ad7cbc9318d22a3981 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 1 May 2026 15:21:05 +1200 Subject: [PATCH 8/8] [ML] Add skeleton GHA workflow for patch version bump workflow_dispatch with branch/new_version/dry_run; GitHub App token for checkout and git push (addresses Buildkite Vault bot 403 class of issues). Document suggested secrets in workflow header. Made-with: Cursor --- .github/workflows/run-patch-release.yml | 115 ++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/run-patch-release.yml diff --git a/.github/workflows/run-patch-release.yml b/.github/workflows/run-patch-release.yml new file mode 100644 index 000000000..a600f2aaf --- /dev/null +++ b/.github/workflows/run-patch-release.yml @@ -0,0 +1,115 @@ +# Skeleton: patch-oriented version bump via GitHub Actions (APM-style entry point). +# Buildkite (or humans) can trigger this with `gh workflow run` once secrets exist. +# +# Required repository secrets (names are suggestions — align with infra / reuse an +# existing org GitHub App after review): +# ML_CPP_RELEASE_GITHUB_APP_ID +# ML_CPP_RELEASE_GITHUB_APP_PRIVATE_KEY (PEM for the app; multiline secret) +# +# Optional (Slack — mirror elastic/apm-server when ready): +# SLACK_BOT_TOKEN +# +# Install the GitHub App on elastic/ml-cpp with contents:write + pull-requests:write +# (same class of permissions as elastic/apm-server run-patch-release.yml). + +name: ml-cpp run patch release + +on: + workflow_dispatch: + inputs: + branch: + description: "Git branch to bump (e.g. 9.5 or feature/foo — must exist on origin)" + required: true + type: string + new_version: + description: "Target elasticsearchVersion in gradle.properties (x.y.z)" + required: true + type: string + dry_run: + description: "If true, bump_version.sh runs with DRY_RUN=true (no git push)" + required: false + type: boolean + default: true + +concurrency: + group: ${{ github.workflow }}-${{ github.run_id }} + +permissions: + contents: read + +jobs: + prepare: + name: Prepare (validate inputs) + runs-on: ubuntu-latest + outputs: + branch: ${{ steps.meta.outputs.branch }} + new_version: ${{ steps.meta.outputs.new_version }} + dry_run: ${{ steps.meta.outputs.dry_run }} + steps: + - name: Validate and emit outputs + id: meta + shell: bash + env: + BRANCH_IN: ${{ inputs.branch }} + VERSION_IN: ${{ inputs.new_version }} + DRY_IN: ${{ inputs.dry_run }} + run: | + set -euo pipefail + if [[ -z "${BRANCH_IN// }" ]]; then + echo "::error::branch must not be empty" + exit 1 + fi + if ! echo "${VERSION_IN}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::new_version must look like major.minor.patch (digits only)" + exit 1 + fi + { + echo "branch=${BRANCH_IN}" + echo "new_version=${VERSION_IN}" + } >> "${GITHUB_OUTPUT}" + if [[ "${DRY_IN}" == "true" ]]; then + echo "dry_run=true" >> "${GITHUB_OUTPUT}" + else + echo "dry_run=false" >> "${GITHUB_OUTPUT}" + fi + + bump: + name: Bump version (GitHub App token) + runs-on: ubuntu-latest + needs: + - prepare + steps: + - name: Mint installation token (GitHub App) + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.ML_CPP_RELEASE_GITHUB_APP_ID }} + private-key: ${{ secrets.ML_CPP_RELEASE_GITHUB_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + + - name: Checkout target branch + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: ${{ needs.prepare.outputs.branch }} + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + # Match dev-tools/bump_version.sh configure_git() so commits stay consistent. + # Swap for elastic/oblt-actions/git/setup@v1 if infra prefers the shared action. + - name: Configure git (service identity) + shell: bash + run: | + git config user.name elasticsearchmachine + git config user.email 'infra-root+elasticsearchmachine@elastic.co' + + - name: Run bump script + env: + NEW_VERSION: ${{ needs.prepare.outputs.new_version }} + BRANCH: ${{ needs.prepare.outputs.branch }} + DRY_RUN: ${{ needs.prepare.outputs.dry_run }} + shell: bash + run: | + set -euo pipefail + bash dev-tools/bump_version.sh